stylelint-plugin-rhythmguard 1.2.1 → 1.4.0
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 +48 -0
- package/README.md +130 -19
- package/assets/campaign-slides/scene-1.png +0 -0
- package/assets/campaign-slides/scene-2.png +0 -0
- package/assets/campaign-slides/scene-3.png +0 -0
- package/assets/campaign-slides/scene-4.png +0 -0
- package/assets/campaign-slides/scene-5.png +0 -0
- package/assets/campaign-slides/scene-6.png +0 -0
- package/assets/rhythmguard-campaign-60s-template.html +322 -0
- package/assets/rhythmguard-campaign-60s.gif +0 -0
- package/assets/rhythmguard-campaign-60s.webm +0 -0
- package/package.json +60 -14
- package/src/configs/expanded.js +27 -0
- package/src/configs/expanded.mjs +4 -0
- package/src/configs/logical.js +16 -0
- package/src/configs/logical.mjs +4 -0
- package/src/configs/migration.js +31 -0
- package/src/configs/migration.mjs +4 -0
- package/src/configs/recommended.mjs +4 -0
- package/src/configs/strict.mjs +4 -0
- package/src/configs/tailwind.mjs +4 -0
- package/src/eslint/index.js +16 -0
- package/src/eslint/index.mjs +8 -0
- package/src/eslint/rules/tailwind-class-use-scale.js +206 -0
- package/src/index.js +4 -0
- package/src/index.mjs +10 -0
- package/src/presets/index.mjs +12 -0
- package/src/rules/no-offscale-transform/index.js +81 -18
- package/src/rules/no-offscale-transform/index.mjs +7 -0
- package/src/rules/prefer-token/index.js +110 -23
- package/src/rules/prefer-token/index.mjs +7 -0
- package/src/rules/use-scale/index.js +80 -19
- package/src/rules/use-scale/index.mjs +7 -0
- package/src/utils/constants.js +81 -13
- package/src/utils/length.js +49 -11
- package/src/utils/options.js +626 -9
- package/src/utils/token-map.js +358 -0
- package/src/utils/value-utils.js +89 -10
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,53 @@ The format follows Keep a Changelog principles and semantic versioning.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.4.0] - 2026-02-21
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- New shared configs:
|
|
14
|
+
- `stylelint-plugin-rhythmguard/configs/expanded`
|
|
15
|
+
- `stylelint-plugin-rhythmguard/configs/logical`
|
|
16
|
+
- `stylelint-plugin-rhythmguard/configs/migration`
|
|
17
|
+
- New ESLint companion export: `stylelint-plugin-rhythmguard/eslint` with rule:
|
|
18
|
+
- `rhythmguard-tailwind/tailwind-class-use-scale`
|
|
19
|
+
- Token migration source automation for `rhythmguard/prefer-token`:
|
|
20
|
+
- `tokenMapFromCssCustomProperties`
|
|
21
|
+
- `tokenMapFile`
|
|
22
|
+
- `tokenMapFromTailwindSpacing` + `tailwindConfigPath`
|
|
23
|
+
- ESM wrapper entry points for package root, configs, presets, rules, and ESLint companion.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Broadened scale enforcement model with built-in property groups:
|
|
28
|
+
- `spacing`, `radius`, `typography`, `size`
|
|
29
|
+
- New per-property override options:
|
|
30
|
+
- `propertyGroups`
|
|
31
|
+
- `propertyScales`
|
|
32
|
+
- New math-function targeting options:
|
|
33
|
+
- `mathFunctionArguments`
|
|
34
|
+
- `ignoreMathFunctionArguments`
|
|
35
|
+
- New `unitStrategy` option (`convert` or `exact`) for non-convertible unit workflows.
|
|
36
|
+
- Compatibility updated to support Stylelint `^16 || ^17`.
|
|
37
|
+
- Tailwind token-map extraction now supports `.js`, `.cjs`, and `.mjs` config files and merges `theme.spacing` with `theme.extend.spacing`.
|
|
38
|
+
|
|
39
|
+
## [1.3.0] - 2026-02-17
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- Strict `secondaryOptions` validation for all three rules:
|
|
44
|
+
- `rhythmguard/use-scale`
|
|
45
|
+
- `rhythmguard/prefer-token`
|
|
46
|
+
- `rhythmguard/no-offscale-transform`
|
|
47
|
+
- Invalid option names (for example `sevverity`) now fail with Stylelint invalid option warnings instead of silently being ignored.
|
|
48
|
+
- Type/shape validation for option payloads (for example `properties` must be an array, `tokenMap` must be an object).
|
|
49
|
+
- Regression tests for invalid secondary option names and option value shapes.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- Added `known-css-properties` as a direct runtime dependency to guarantee `properties` option validation in consumer installs.
|
|
54
|
+
- `properties` option validation now checks supported spacing property names against known CSS property metadata (plus `translate-x`, `translate-y`, `translate-z`).
|
|
55
|
+
|
|
9
56
|
## [1.2.1] - 2026-02-17
|
|
10
57
|
|
|
11
58
|
### Fixed
|
|
@@ -15,6 +62,7 @@ The format follows Keep a Changelog principles and semantic versioning.
|
|
|
15
62
|
- `rhythmguard/prefer-token` now supports `enforceInsideMathFunctions` for optional math-function enforcement.
|
|
16
63
|
- Hardened `var()` token argument detection to parse the first argument structurally (rather than comma string splitting).
|
|
17
64
|
- npm README link integrity: docs links now resolve to absolute GitHub URLs from the npm package page.
|
|
65
|
+
- Release workflow now detects missing `NPM_TOKEN` and skips publish cleanly with an explicit notice instead of failing.
|
|
18
66
|
|
|
19
67
|
### Added
|
|
20
68
|
|
package/README.md
CHANGED
|
@@ -12,13 +12,22 @@ High-precision spacing governance for CSS and design systems.
|
|
|
12
12
|
[](./LICENSE)
|
|
13
13
|
[](https://nodejs.org/)
|
|
14
14
|
|
|
15
|
-
`stylelint-plugin-rhythmguard` enforces
|
|
15
|
+
`stylelint-plugin-rhythmguard` enforces scale and token discipline across spacing, radius, typography, size, and translate motion offsets.
|
|
16
|
+
|
|
17
|
+
## Demo
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<a href="./assets/rhythmguard-campaign-60s.webm">
|
|
21
|
+
<img src="./assets/rhythmguard-campaign-60s.gif" width="100%" alt="Rhythmguard 60-second demo" />
|
|
22
|
+
</a>
|
|
23
|
+
</p>
|
|
16
24
|
|
|
17
25
|
I built Rhythmguard after 20 years of watching teams ignore spacing scales and ship arbitrary pixel values everywhere.
|
|
18
26
|
|
|
19
27
|
It is built for teams that want:
|
|
20
28
|
|
|
21
29
|
- zero random spacing values in production CSS
|
|
30
|
+
- consistent numeric scales for radius, typography, and sizing primitives
|
|
22
31
|
- token-first spacing workflows
|
|
23
32
|
- predictable autofix behavior for large migrations
|
|
24
33
|
- consistent layout rhythm across web surfaces
|
|
@@ -69,11 +78,44 @@ npm install --save-dev stylelint stylelint-plugin-rhythmguard
|
|
|
69
78
|
}
|
|
70
79
|
```
|
|
71
80
|
|
|
81
|
+
### Expanded config
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"extends": ["stylelint-plugin-rhythmguard/configs/expanded"]
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`expanded` enables scale enforcement for spacing + radius + typography + size property groups.
|
|
90
|
+
|
|
91
|
+
### Logical config
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"extends": ["stylelint-plugin-rhythmguard/configs/logical"]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`logical` composes Rhythmguard strict mode with `stylelint-plugin-logical-css` recommended rules.
|
|
100
|
+
|
|
101
|
+
### Migration config
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"extends": ["stylelint-plugin-rhythmguard/configs/migration"]
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`migration` keeps on-scale numeric values temporarily while auto-building token mappings from CSS custom properties and optional Tailwind spacing config.
|
|
110
|
+
|
|
72
111
|
Stable shared config entry points:
|
|
73
112
|
|
|
74
113
|
- `stylelint-plugin-rhythmguard/configs/recommended`
|
|
75
114
|
- `stylelint-plugin-rhythmguard/configs/strict`
|
|
76
115
|
- `stylelint-plugin-rhythmguard/configs/tailwind`
|
|
116
|
+
- `stylelint-plugin-rhythmguard/configs/expanded`
|
|
117
|
+
- `stylelint-plugin-rhythmguard/configs/logical`
|
|
118
|
+
- `stylelint-plugin-rhythmguard/configs/migration`
|
|
77
119
|
|
|
78
120
|
### Full custom setup
|
|
79
121
|
|
|
@@ -85,13 +127,22 @@ Stable shared config entry points:
|
|
|
85
127
|
true,
|
|
86
128
|
{
|
|
87
129
|
"preset": "rhythmic-4",
|
|
130
|
+
"propertyGroups": ["spacing", "radius"],
|
|
131
|
+
"propertyScales": {
|
|
132
|
+
"font-size": [12, 14, 16, 20, 24]
|
|
133
|
+
},
|
|
88
134
|
"units": ["px", "rem", "em"],
|
|
135
|
+
"unitStrategy": "convert",
|
|
89
136
|
"baseFontSize": 16,
|
|
90
137
|
"tokenPattern": "^--space-",
|
|
91
138
|
"tokenFunctions": ["var", "theme", "token"],
|
|
92
139
|
"allowNegative": true,
|
|
93
140
|
"allowPercentages": true,
|
|
94
|
-
"fixToScale": true
|
|
141
|
+
"fixToScale": true,
|
|
142
|
+
"enforceInsideMathFunctions": true,
|
|
143
|
+
"mathFunctionArguments": {
|
|
144
|
+
"clamp": [1, 3]
|
|
145
|
+
}
|
|
95
146
|
}
|
|
96
147
|
],
|
|
97
148
|
"rhythmguard/prefer-token": [
|
|
@@ -99,6 +150,9 @@ Stable shared config entry points:
|
|
|
99
150
|
{
|
|
100
151
|
"tokenPattern": "^--space-",
|
|
101
152
|
"allowNumericScale": false,
|
|
153
|
+
"tokenMapFromCssCustomProperties": true,
|
|
154
|
+
"tokenMapFromTailwindSpacing": true,
|
|
155
|
+
"tailwindConfigPath": "./tailwind.config.mjs",
|
|
102
156
|
"tokenMap": {
|
|
103
157
|
"4px": "var(--space-1)",
|
|
104
158
|
"8px": "var(--space-2)",
|
|
@@ -146,6 +200,26 @@ Scale resolution precedence:
|
|
|
146
200
|
3. `preset`
|
|
147
201
|
4. default `rhythmic-4` scale
|
|
148
202
|
|
|
203
|
+
## Option Validation
|
|
204
|
+
|
|
205
|
+
Rhythmguard validates `secondaryOptions` for each rule before linting declarations.
|
|
206
|
+
|
|
207
|
+
- Unknown option names fail fast with Stylelint invalid option warnings.
|
|
208
|
+
- Invalid option shapes fail fast (for example string vs array mismatches).
|
|
209
|
+
- `properties` string entries are validated against supported scale-targetable CSS property names.
|
|
210
|
+
- `propertyGroups` values are validated against built-in groups: `spacing`, `radius`, `typography`, and `size`.
|
|
211
|
+
- Math function argument maps are validated per function (`calc`, `clamp`, `min`, `max`) and positive 1-based argument indexes.
|
|
212
|
+
|
|
213
|
+
Example typo that now fails immediately:
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"rules": {
|
|
218
|
+
"rhythmguard/use-scale": [true, { "sevverity": "warning" }]
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
149
223
|
## Built-in Scale Presets
|
|
150
224
|
|
|
151
225
|
| Preset | Pattern | Scale |
|
|
@@ -235,6 +309,10 @@ Checks:
|
|
|
235
309
|
- `inset*`, `scroll-margin*`, `scroll-padding*`
|
|
236
310
|
- `translate`, `translate-x`, `translate-y`, `translate-z`
|
|
237
311
|
- `transform` translation functions (`translate`, `translateX`, `translateY`, `translateZ`, `translate3d`)
|
|
312
|
+
- optional property groups:
|
|
313
|
+
- `radius` (`border-radius*`, corner radii, `outline-offset`)
|
|
314
|
+
- `typography` (`font-size`, `line-height`, `letter-spacing`, `word-spacing`)
|
|
315
|
+
- `size` (`width`, `height`, min/max size, logical `inline-size`/`block-size`)
|
|
238
316
|
|
|
239
317
|
Example:
|
|
240
318
|
|
|
@@ -260,6 +338,7 @@ Options:
|
|
|
260
338
|
| `customScale` | `Array<number|string>` | `undefined` | Highest-priority custom scale override |
|
|
261
339
|
| `scale` | `Array<number|string>` | `[0,4,8,12,16,24,32,40,48,64]` | Allowed spacing values |
|
|
262
340
|
| `units` | `string[]` | `['px','rem','em']` | Units considered for scale enforcement |
|
|
341
|
+
| `unitStrategy` | `'convert' \| 'exact'` | `'convert'` | `convert`: compare via px conversion (`px/rem/em`). `exact`: compare against same-unit scale values (for example `vw`, `cqi`) |
|
|
263
342
|
| `baseFontSize` | `number` | `16` | Used for `rem`/`em` conversion |
|
|
264
343
|
| `tokenPattern` | `string` | `^--space-` | Regex for accepted token variable names |
|
|
265
344
|
| `tokenFunctions` | `string[]` | `['var','theme','token']` | Functions treated as tokenized values |
|
|
@@ -267,7 +346,11 @@ Options:
|
|
|
267
346
|
| `allowPercentages` | `boolean` | `true` | Allows `%` values without scale checks |
|
|
268
347
|
| `fixToScale` | `boolean` | `true` | Enables nearest-value autofix |
|
|
269
348
|
| `enforceInsideMathFunctions` | `boolean` | `false` | Lints `calc()/clamp()/min()/max()` internals |
|
|
270
|
-
| `
|
|
349
|
+
| `mathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Restricts linting to specific 1-based argument indexes per math function |
|
|
350
|
+
| `ignoreMathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Excludes specific 1-based argument indexes per math function |
|
|
351
|
+
| `propertyGroups` | `Array<'spacing' \| 'radius' \| 'typography' \| 'size'>` | `['spacing']` | Selects built-in property groups when `properties` is not provided |
|
|
352
|
+
| `properties` | `Array<string|RegExp>` | built-in spacing patterns | Override targeted property set; string values must be supported spacing property names |
|
|
353
|
+
| `propertyScales` | `Record<propertyOrRegex, scaleOrPreset>` | `{}` | Per-property scale overrides (supports exact names or `/regex/flags` keys) |
|
|
271
354
|
|
|
272
355
|
### `rhythmguard/prefer-token`
|
|
273
356
|
|
|
@@ -300,10 +383,20 @@ Options:
|
|
|
300
383
|
| `customScale` | `Array<number|string>` | `undefined` | Highest-priority custom scale override |
|
|
301
384
|
| `scale` | `Array<number|string>` | `[0,4,8,12,16,24,32,40,48,64]` | Used when `allowNumericScale` is enabled |
|
|
302
385
|
| `baseFontSize` | `number` | `16` | Used for scale checks with `rem`/`em` |
|
|
386
|
+
| `unitStrategy` | `'convert' \| 'exact'` | `'convert'` | Matching strategy when `allowNumericScale` is enabled |
|
|
387
|
+
| `units` | `string[]` | `['px','rem','em']` | Units considered for numeric scale checks |
|
|
303
388
|
| `enforceInsideMathFunctions` | `boolean` | `false` | Lints `calc()/clamp()/min()/max()` internals |
|
|
389
|
+
| `mathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Restricts linting to specific 1-based argument indexes per math function |
|
|
390
|
+
| `ignoreMathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Excludes specific 1-based argument indexes per math function |
|
|
304
391
|
| `tokenMap` | `Record<string,string>` | `{}` | Enables autofix from raw value to token |
|
|
392
|
+
| `tokenMapFile` | `string` | `null` | JSON file path to merge additional token mappings |
|
|
393
|
+
| `tokenMapFromCssCustomProperties` | `boolean` | `false` | Auto-builds mappings from matching custom property declarations in the same stylesheet |
|
|
394
|
+
| `tokenMapFromTailwindSpacing` | `boolean` | `false` | Auto-builds mappings from `theme.spacing` and `theme.extend.spacing` in Tailwind config |
|
|
395
|
+
| `tailwindConfigPath` | `string` | `null` | Path to Tailwind config used by `tokenMapFromTailwindSpacing` (`.js`, `.cjs`, `.mjs`) |
|
|
305
396
|
| `ignoreValues` | `string[]` | CSS global keywords + `auto` | Skips keyword literals |
|
|
306
|
-
| `
|
|
397
|
+
| `propertyGroups` | `Array<'spacing' \| 'radius' \| 'typography' \| 'size'>` | `['spacing']` | Selects built-in property groups when `properties` is not provided |
|
|
398
|
+
| `properties` | `Array<string|RegExp>` | built-in spacing patterns | Override targeted property set; string values must be supported spacing property names |
|
|
399
|
+
| `propertyScales` | `Record<propertyOrRegex, scaleOrPreset>` | `{}` | Per-property scale overrides for numeric migration mode |
|
|
307
400
|
|
|
308
401
|
### `rhythmguard/no-offscale-transform`
|
|
309
402
|
|
|
@@ -325,7 +418,7 @@ Example:
|
|
|
325
418
|
|
|
326
419
|
Options:
|
|
327
420
|
|
|
328
|
-
`rhythmguard/no-offscale-transform` accepts the same scale options as `rhythmguard/use-scale`, but only for transform translation properties.
|
|
421
|
+
`rhythmguard/no-offscale-transform` accepts the same scale options as `rhythmguard/use-scale` (including `unitStrategy`, math argument targeting, and deterministic autofix), but only for transform translation properties. Its secondary options are also validated for unknown keys and invalid value shapes.
|
|
329
422
|
|
|
330
423
|
## Tailwind CSS Integration
|
|
331
424
|
|
|
@@ -343,7 +436,29 @@ Rhythmguard works well in Tailwind projects, but it enforces what Stylelint can
|
|
|
343
436
|
- `class="p-4 gap-2"`
|
|
344
437
|
- `class="p-[13px] translate-y-[18px]"`
|
|
345
438
|
|
|
346
|
-
Those are not Stylelint declaration nodes, so they are outside
|
|
439
|
+
Those are not Stylelint declaration nodes, so they are outside Stylelint rule scope.
|
|
440
|
+
|
|
441
|
+
### Companion ESLint layer for class strings
|
|
442
|
+
|
|
443
|
+
Rhythmguard now ships an ESLint companion export for class-string governance:
|
|
444
|
+
|
|
445
|
+
```js
|
|
446
|
+
// eslint.config.js (flat config)
|
|
447
|
+
import rhythmguard from 'stylelint-plugin-rhythmguard/eslint';
|
|
448
|
+
|
|
449
|
+
export default [
|
|
450
|
+
{
|
|
451
|
+
plugins: {
|
|
452
|
+
'rhythmguard-tailwind': rhythmguard,
|
|
453
|
+
},
|
|
454
|
+
rules: {
|
|
455
|
+
'rhythmguard-tailwind/tailwind-class-use-scale': ['error', { scale: [0, 4, 8, 12, 16, 24, 32] }],
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
];
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
This rule targets arbitrary spacing utilities such as `p-[13px]`, `gap-[18px]`, `translate-x-[10px]`, and autofixes to the nearest configured scale value.
|
|
347
462
|
|
|
348
463
|
### Recommended stack for full Tailwind enforcement
|
|
349
464
|
|
|
@@ -362,7 +477,8 @@ Suggested setup:
|
|
|
362
477
|
|
|
363
478
|
Then pair with:
|
|
364
479
|
|
|
365
|
-
- `
|
|
480
|
+
- `stylelint-plugin-rhythmguard/eslint` for arbitrary spacing class-string scale enforcement.
|
|
481
|
+
- `eslint-plugin-tailwindcss` for broader class-string linting and conventions.
|
|
366
482
|
- `prettier-plugin-tailwindcss` for deterministic class ordering.
|
|
367
483
|
|
|
368
484
|
Detailed setup reference: [`docs/TAILWIND.md`](https://github.com/PetriLahdelma/stylelint-plugin-rhythmguard/blob/main/docs/TAILWIND.md).
|
|
@@ -371,14 +487,7 @@ Detailed setup reference: [`docs/TAILWIND.md`](https://github.com/PetriLahdelma/
|
|
|
371
487
|
|
|
372
488
|
By default, `tokenFunctions` includes `theme`, so values like `theme(spacing.4)` are treated as tokenized values.
|
|
373
489
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
We should extend Tailwind coverage thoroughly, but in the right architecture:
|
|
377
|
-
|
|
378
|
-
- keep `stylelint-plugin-rhythmguard` focused on CSS declaration enforcement
|
|
379
|
-
- add a complementary Tailwind class-string layer (ESLint/plugin side) for utility classes
|
|
380
|
-
|
|
381
|
-
This avoids brittle parsing hacks and gives full coverage without compromising rule quality.
|
|
490
|
+
This keeps CSS declaration enforcement and template class-string enforcement separated but coordinated.
|
|
382
491
|
|
|
383
492
|
## Programmatic Presets
|
|
384
493
|
|
|
@@ -389,6 +498,7 @@ console.log(rhythmguard.presets.listScalePresetNames());
|
|
|
389
498
|
console.log(rhythmguard.presets.listCommunityScalePresetNames());
|
|
390
499
|
console.log(rhythmguard.presets.getCommunityScaleMetadata('product-decimal-10'));
|
|
391
500
|
console.log(rhythmguard.presets.scales['rhythmic-4']);
|
|
501
|
+
console.log(Object.keys(rhythmguard.eslint.rules));
|
|
392
502
|
```
|
|
393
503
|
|
|
394
504
|
## Autofix Philosophy
|
|
@@ -402,9 +512,9 @@ It will not guess token mappings without your map.
|
|
|
402
512
|
|
|
403
513
|
## Compatibility
|
|
404
514
|
|
|
405
|
-
- Stylelint: `^16.0.0`
|
|
515
|
+
- Stylelint: `^16.0.0 || ^17.0.0`
|
|
406
516
|
- Node.js: `>=18.18.0`
|
|
407
|
-
- Module format: CommonJS
|
|
517
|
+
- Module format: dual `require` + `import` entry points (CommonJS + ESM wrappers)
|
|
408
518
|
- Note: Stylelint `16.0.0` has known autofix/API behavior differences; CI enforces floor compatibility and runs non-blocking full-suite observability on the floor version.
|
|
409
519
|
|
|
410
520
|
## Development
|
|
@@ -441,8 +551,9 @@ Detailed methodology and custom args are documented in [`docs/BENCHMARKING.md`](
|
|
|
441
551
|
1. Create a GitHub release.
|
|
442
552
|
2. `release.yml` runs the Node/Stylelint matrix validation.
|
|
443
553
|
3. A tarball smoke test validates package exports and install behavior.
|
|
444
|
-
4.
|
|
445
|
-
5. `
|
|
554
|
+
4. If `NPM_TOKEN` is configured in repository secrets, the package is published to npm with provenance (`npm publish --provenance`).
|
|
555
|
+
5. If `NPM_TOKEN` is not configured, publish is skipped with an explicit workflow notice.
|
|
556
|
+
6. `post-publish-smoke.yml` verifies the published npm version can be installed and run in a clean project (and skips cleanly if the version is not on npm).
|
|
446
557
|
|
|
447
558
|
## Support and Bug Reports
|
|
448
559
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Rhythmguard 60s Campaign Frame</title>
|
|
7
|
+
<style>
|
|
8
|
+
@font-face {
|
|
9
|
+
font-family: "GeistPixel";
|
|
10
|
+
src: url("../node_modules/geist/dist/fonts/geist-pixel/GeistPixel-Line.woff2") format("woff2");
|
|
11
|
+
font-weight: 400;
|
|
12
|
+
font-style: normal;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
:root {
|
|
16
|
+
--bg: #000;
|
|
17
|
+
--fg: #fff;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
* {
|
|
21
|
+
box-sizing: border-box;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
html,
|
|
25
|
+
body {
|
|
26
|
+
width: 1920px;
|
|
27
|
+
height: 1080px;
|
|
28
|
+
margin: 0;
|
|
29
|
+
padding: 0;
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
background: var(--bg);
|
|
32
|
+
color: var(--fg);
|
|
33
|
+
font-family: "GeistPixel", monospace;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
body {
|
|
37
|
+
padding: 28px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.frame {
|
|
41
|
+
width: 100%;
|
|
42
|
+
height: 100%;
|
|
43
|
+
border: 4px solid var(--fg);
|
|
44
|
+
display: grid;
|
|
45
|
+
grid-template-rows: 118px 1fr 110px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.header {
|
|
49
|
+
border-bottom: 2px solid var(--fg);
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: space-between;
|
|
53
|
+
padding: 0 30px;
|
|
54
|
+
font-size: 34px;
|
|
55
|
+
letter-spacing: 0.5px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.main {
|
|
59
|
+
display: grid;
|
|
60
|
+
grid-template-columns: minmax(0, 2.1fr) minmax(0, 1fr);
|
|
61
|
+
gap: 28px;
|
|
62
|
+
padding: 28px 30px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.hero {
|
|
66
|
+
border: 2px solid var(--fg);
|
|
67
|
+
padding: 26px;
|
|
68
|
+
display: grid;
|
|
69
|
+
grid-template-rows: auto auto auto 1fr;
|
|
70
|
+
gap: 18px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.kicker {
|
|
74
|
+
font-size: 30px;
|
|
75
|
+
letter-spacing: 0.8px;
|
|
76
|
+
margin: 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.title {
|
|
80
|
+
font-size: 96px;
|
|
81
|
+
letter-spacing: 1px;
|
|
82
|
+
line-height: 1;
|
|
83
|
+
margin: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.subtitle {
|
|
87
|
+
font-size: 42px;
|
|
88
|
+
letter-spacing: 0.8px;
|
|
89
|
+
margin: 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.terminal {
|
|
93
|
+
border: 2px solid var(--fg);
|
|
94
|
+
display: grid;
|
|
95
|
+
grid-template-rows: 50px 1fr;
|
|
96
|
+
min-height: 360px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.terminal-bar {
|
|
100
|
+
border-bottom: 2px solid var(--fg);
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
justify-content: space-between;
|
|
104
|
+
padding: 0 16px;
|
|
105
|
+
font-size: 24px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.terminal-dots {
|
|
109
|
+
display: flex;
|
|
110
|
+
gap: 10px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.terminal-dot {
|
|
114
|
+
width: 12px;
|
|
115
|
+
height: 12px;
|
|
116
|
+
border: 2px solid var(--fg);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
pre {
|
|
120
|
+
margin: 0;
|
|
121
|
+
padding: 16px;
|
|
122
|
+
font-family: "GeistPixel", monospace;
|
|
123
|
+
font-size: 30px;
|
|
124
|
+
line-height: 1.45;
|
|
125
|
+
white-space: pre-wrap;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.side {
|
|
129
|
+
display: grid;
|
|
130
|
+
grid-template-rows: 1fr 1fr;
|
|
131
|
+
gap: 18px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.card {
|
|
135
|
+
border: 2px solid var(--fg);
|
|
136
|
+
padding: 18px;
|
|
137
|
+
display: grid;
|
|
138
|
+
grid-template-rows: auto 1fr;
|
|
139
|
+
gap: 12px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.card h2 {
|
|
143
|
+
margin: 0;
|
|
144
|
+
font-size: 36px;
|
|
145
|
+
letter-spacing: 0.8px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.rules {
|
|
149
|
+
margin: 0;
|
|
150
|
+
padding-left: 24px;
|
|
151
|
+
font-size: 31px;
|
|
152
|
+
line-height: 1.5;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.meta {
|
|
156
|
+
margin: 0;
|
|
157
|
+
font-size: 30px;
|
|
158
|
+
line-height: 1.5;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.footer {
|
|
162
|
+
border-top: 2px solid var(--fg);
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
justify-content: space-between;
|
|
166
|
+
padding: 0 30px;
|
|
167
|
+
font-size: 30px;
|
|
168
|
+
}
|
|
169
|
+
</style>
|
|
170
|
+
</head>
|
|
171
|
+
<body>
|
|
172
|
+
<div class="frame">
|
|
173
|
+
<header class="header">
|
|
174
|
+
<div id="step">STEP 1 / 6</div>
|
|
175
|
+
<div>Rhythmguard</div>
|
|
176
|
+
</header>
|
|
177
|
+
<main class="main">
|
|
178
|
+
<section class="hero">
|
|
179
|
+
<p class="kicker" id="kicker">INSTALL + STRICT CONFIG</p>
|
|
180
|
+
<h1 class="title" id="title">NO RANDOM 13PX.</h1>
|
|
181
|
+
<p class="subtitle" id="subtitle">SCALE OR TOKEN. PERIOD.</p>
|
|
182
|
+
<div class="terminal">
|
|
183
|
+
<div class="terminal-bar">
|
|
184
|
+
<div class="terminal-dots">
|
|
185
|
+
<span class="terminal-dot"></span>
|
|
186
|
+
<span class="terminal-dot"></span>
|
|
187
|
+
<span class="terminal-dot"></span>
|
|
188
|
+
</div>
|
|
189
|
+
<span id="terminal-title">terminal // setup</span>
|
|
190
|
+
</div>
|
|
191
|
+
<pre id="code"></pre>
|
|
192
|
+
</div>
|
|
193
|
+
</section>
|
|
194
|
+
<aside class="side">
|
|
195
|
+
<section class="card">
|
|
196
|
+
<h2>RULES</h2>
|
|
197
|
+
<ul class="rules">
|
|
198
|
+
<li>use-scale</li>
|
|
199
|
+
<li>prefer-token</li>
|
|
200
|
+
<li>no-offscale-transform</li>
|
|
201
|
+
</ul>
|
|
202
|
+
</section>
|
|
203
|
+
<section class="card">
|
|
204
|
+
<h2>WHY</h2>
|
|
205
|
+
<p class="meta" id="why"></p>
|
|
206
|
+
</section>
|
|
207
|
+
</aside>
|
|
208
|
+
</main>
|
|
209
|
+
<footer class="footer">
|
|
210
|
+
<div>stylelint-plugin-rhythmguard</div>
|
|
211
|
+
</footer>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<script>
|
|
215
|
+
const scenes = [
|
|
216
|
+
{
|
|
217
|
+
kicker: "INSTALL + STRICT CONFIG",
|
|
218
|
+
title: "NO RANDOM 13PX.",
|
|
219
|
+
subtitle: "SCALE OR TOKEN. PERIOD.",
|
|
220
|
+
terminalTitle: "terminal // setup",
|
|
221
|
+
code: [
|
|
222
|
+
"$ npm i -D stylelint stylelint-plugin-rhythmguard",
|
|
223
|
+
"$ cat .stylelintrc.json",
|
|
224
|
+
"{",
|
|
225
|
+
' "extends": ["stylelint-plugin-rhythmguard/configs/strict"]',
|
|
226
|
+
"}",
|
|
227
|
+
'$ npx stylelint "src/**/*.css"',
|
|
228
|
+
],
|
|
229
|
+
why: "Shared config replaces ad-hoc spacing rules across files.",
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
kicker: "RULE 1 // USE-SCALE",
|
|
233
|
+
title: "LOCK TO SCALE.",
|
|
234
|
+
subtitle: "EVERY SPACING VALUE GETS CHECKED.",
|
|
235
|
+
terminalTitle: "report // use-scale",
|
|
236
|
+
code: [
|
|
237
|
+
"Button.css:12",
|
|
238
|
+
" Expected spacing from configured scale.",
|
|
239
|
+
" ✕ margin-top: 13px",
|
|
240
|
+
" ✓ margin-top: 12px",
|
|
241
|
+
"",
|
|
242
|
+
"Autofix maps to nearest safe scale step.",
|
|
243
|
+
],
|
|
244
|
+
why: "Scale discipline keeps layouts predictable in large systems.",
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
kicker: "RULE 2 // PREFER-TOKEN",
|
|
248
|
+
title: "TOKENS FIRST.",
|
|
249
|
+
subtitle: "RAW PIXELS BECOME DESIGN TOKENS.",
|
|
250
|
+
terminalTitle: "report // prefer-token",
|
|
251
|
+
code: [
|
|
252
|
+
"Card.css:8",
|
|
253
|
+
" Expected token, not raw literal.",
|
|
254
|
+
" ✕ padding: 16px",
|
|
255
|
+
" ✓ padding: var(--space-4)",
|
|
256
|
+
"",
|
|
257
|
+
"tokenMap applies safe automatic replacements.",
|
|
258
|
+
],
|
|
259
|
+
why: "Token usage makes theme changes and audits low-risk.",
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
kicker: "RULE 3 // TRANSFORM OFFSETS",
|
|
263
|
+
title: "MOTION IN RHYTHM.",
|
|
264
|
+
subtitle: "TRANSLATES STAY ON THE SAME SCALE.",
|
|
265
|
+
terminalTitle: "report // no-offscale-transform",
|
|
266
|
+
code: [
|
|
267
|
+
"Toast.css:22",
|
|
268
|
+
" Off-scale translate value detected.",
|
|
269
|
+
" ✕ transform: translateY(7px)",
|
|
270
|
+
" ✓ transform: translateY(8px)",
|
|
271
|
+
"",
|
|
272
|
+
"Motion offsets align with layout spacing values.",
|
|
273
|
+
],
|
|
274
|
+
why: "Spacing + motion consistency improves visual quality.",
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
kicker: "AUTOFIX WORKFLOW",
|
|
278
|
+
title: "FIX FAST, SAFELY.",
|
|
279
|
+
subtitle: "MIGRATE LEGACY CSS WITHOUT DRAMA.",
|
|
280
|
+
terminalTitle: "autofix // migration",
|
|
281
|
+
code: [
|
|
282
|
+
"$ npx stylelint \"src/**/*.css\" --fix",
|
|
283
|
+
"Applied fixes:",
|
|
284
|
+
" - use-scale: 124",
|
|
285
|
+
" - prefer-token: 98",
|
|
286
|
+
" - no-offscale-transform: 19",
|
|
287
|
+
"",
|
|
288
|
+
"Review diff and ship.",
|
|
289
|
+
],
|
|
290
|
+
why: "Autofix handles repetitive cleanup at production scale.",
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
kicker: "READY TO SHIP",
|
|
294
|
+
title: "RHYTHMGUARD",
|
|
295
|
+
subtitle: "SPACING GOVERNANCE FOR REAL-WORLD TEAMS.",
|
|
296
|
+
terminalTitle: "next // adopt",
|
|
297
|
+
code: [
|
|
298
|
+
"Extends available:",
|
|
299
|
+
" - configs/recommended",
|
|
300
|
+
" - configs/strict",
|
|
301
|
+
" - configs/tailwind",
|
|
302
|
+
"",
|
|
303
|
+
"No random spacing values in production CSS.",
|
|
304
|
+
],
|
|
305
|
+
why: "Consistent spacing rules reduce regressions release after release.",
|
|
306
|
+
},
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
const sceneQuery = Number(new URLSearchParams(window.location.search).get("scene") || "1");
|
|
310
|
+
const index = Math.min(Math.max(sceneQuery, 1), scenes.length) - 1;
|
|
311
|
+
const scene = scenes[index];
|
|
312
|
+
|
|
313
|
+
document.getElementById("step").textContent = `STEP ${index + 1} / ${scenes.length}`;
|
|
314
|
+
document.getElementById("kicker").textContent = scene.kicker;
|
|
315
|
+
document.getElementById("title").textContent = scene.title;
|
|
316
|
+
document.getElementById("subtitle").textContent = scene.subtitle;
|
|
317
|
+
document.getElementById("terminal-title").textContent = scene.terminalTitle;
|
|
318
|
+
document.getElementById("code").textContent = scene.code.join("\n");
|
|
319
|
+
document.getElementById("why").textContent = scene.why;
|
|
320
|
+
</script>
|
|
321
|
+
</body>
|
|
322
|
+
</html>
|
|
Binary file
|
|
Binary file
|