Uh oh!
There was an error while loading. Please reload this page.
Upgrade the lint and format toolchain: Prettier 3, ESLint 9, Stylelint 16 - #2416
Merged
Conversation
Prettier 3 changes the `trailingComma` default from `es5` to `all`, and breaks multi-value CSS declarations onto their own lines. Mechanical -- `dist` is byte-for-byte identical before and after.
Prettier 3 does not fix the MDX corruption `.prettierignore` blamed on Prettier 2 -- it has no MDX 3 support either (prettier/prettier#12209), and mangles multi-line `{/* ... */}` comments into invalid `{/_ ... _/}` (prettier/prettier#15163). There is no per-file opt out: `prettier-ignore` has no effect, and the HTML-comment form Prettier honours is not valid MDX. Only multi-line comments are affected, so the other 80 files were formatted by hand and the rule reinstated with the real reason recorded.
Replaces `@cloudfour/eslint-plugin`, which is superseded by `@cloudfour/eslint-config`, and moves to flat config: `.eslintrc.js` and `.eslintignore` become `eslint.config.mjs`. This lifts the ceiling the plugin imposed. The plugin peers on `eslint: ^8.0.0` even at its latest release; the config peers on `>= 9` and bundles typescript-eslint 8, so ESLint moves 8.33 -> 9.39.5 and the plugins it wraps jump several majors (unicorn 47 -> 63, jsdoc 46 -> 62, n 17). We stop at ESLint 9 rather than 10 because eslint-plugin-react, which supplies the `jsx-uses-vars` rule the MDX files need, peers at `^9.7` and has no 10 support. Rule names lose the `@cloudfour/` prefix, since the config registers plugins under their own names -- `@cloudfour/n/...` becomes `n/...`, and `@cloudfour/typescript-eslint/...` becomes `@typescript-eslint/...`. Only two eslint-disable comments referenced the old names. `eslint-plugin-mdx` moves 1 -> 3 in the same change, because it is the same knot. v1 set `eslint-mdx` as the parser for *every* file rather than just Markdown, which is what the "eslint isn't merging the overrides as expected" comment in the old config was working around, and what made `unicorn/no-empty-file` report four token files whose only statement is `module.exports`. With v3 scoping its parser correctly, `.ts` files pick up `@typescript-eslint/parser` from the shared config on their own, so that override is gone. Three config decisions worth recording: - `settings.n.version` is pinned to our development Node. Without it the `n/no-unsupported-features/*` rules fall back to `engines.node`, which describes what consumers of the published package need -- so our gulpfile, Storybook config and tests were being checked against Node 12. Setting the lint target here avoids changing a consumer-facing field as a side effect. - Plain Markdown now reaches the JS rules, where rules written for code misfire on prose. `no-irregular-whitespace` flags the non-breaking spaces indenting the directory tree in CONTRIBUTING.md and `filename-case` wants README.md renamed, so both are off for `*.md`, which `mdx/remark` lints instead. - `unicorn/prefer-global-this` is off for `src`. Its auto-fix rewrites `window.setTimeout` to `globalThis.setTimeout`, which resolves to Node's overload and returns a `Timeout` rather than the `number` these components store -- a type error that only `tsc` caught. In browser-only code `window` is both correct and better typed. The rest is the new rules' findings: JSDoc types where they were missing or `any`, `replaceAll` over global-regex `replace`, spread over `concat`, `path.join` over string concatenation, named default exports, and import order.
stylelint 14 -> 16.26.1, stylelint-config-cloudfour 6 -> 10, stylelint-scss 4 -> 7, stylelint-use-logical-spec 4 -> 5. Stylelint stops at 16, not 17 as the plan assumed. Both stylelint-config-cloudfour 10 and stylelint-use-logical-spec cap there independently, so 17 would mean running our own shared config unsupported. `stylelint-config-prettier` is removed rather than upgraded: stylelint 15 deprecated its stylistic rules and 16 removed them, leaving the package with nothing to turn off, and it peers on `stylelint < 15` anyway. That retirement did not extend to stylelint-scss, which still ships and enables all of the rules the old config listed by hand -- three of them fire on Prettier's own output -- so those disables stay, with the comment rewritten to say why they outlived the package that motivated them. Config changes for stylelint 16's new checks: - `media-feature-name-no-unknown` reads the Sass module namespace in `@media (width >= breakpoint.$l)` as a feature name. It handles a bare `$variable` in range syntax but not a namespaced one, and only in the range form -- `(min-width: breakpoint.$m)` parses fine. Ignored by name. - `declaration-block-no-redundant-longhand-properties` now reports gap under its modern name, so the existing `grid-*-gap` exemption no longer matched. Added the block/inline shorthands too, which utilities/spacing documents avoiding for browser support. Three genuine findings fixed at the source: a redundant leading underscore in a partial name, empty parentheses on an argumentless mixin include, and a collapsible `place-self` pair. Only the last changes published CSS, and only to an equivalent declaration, so it carries a patch changeset.
The addon was dropped during the Storybook 10 migration and could not come back until now: v9 supports Storybook 10 but peers on `prettier ^3.7.1`, which it uses to pretty-print the markup it displays. `storybook-mobile`, the other addon parked at the same time, stays dropped. It is still at 1.0.1 and peers on React ^17 while Storybook 10 is on 19.
🦋 Changeset detectedLatest commit: 2dc1e55 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
✅ Deploy Preview for cloudfour-patterns ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
This was referenced Aug 21, 2026
Uh oh!
There was an error while loading. Please reload this page.
Merged
This was referenced Aug 21, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This is item 4 of the dependency modernization sequence: Prettier 3, the ESLint
toolchain, and Stylelint 16. It has knock-on value in two directions. Prettier 3 is what
lets
@whitespace/storybook-addon-htmlcome back, since v9 supports Storybook 10 butpeers on
prettier ^3.7.1to pretty-print the markup it displays. And moving off@cloudfour/eslint-pluginonto its successor@cloudfour/eslint-configlifts the ESLintceiling that the plan had listed as blocking, which in turn is what gated moving past
TypeScript 5.
The plugin peers on
eslint: ^8.0.0even at its latest release. The config peers on>= 9and bundles typescript-eslint 8, so ESLint moves 8.33 → 9.39.5 and the plugins itwraps jump several majors (unicorn 47 → 63, jsdoc 46 → 62, n 17). That also means flat
config:
.eslintrc.jsand.eslintignorebecomeeslint.config.mjs, and rule names losethe
@cloudfour/prefix now that plugins register under their own names. We stop atESLint 9 rather than 10 because
eslint-plugin-react— which supplies thejsx-uses-varsrule the MDX files rely on — peers at^9.7with no 10 support.Stylelint stops at 16, not 17 as the plan assumed:
stylelint-config-cloudfour10 andstylelint-use-logical-specboth cap there independently, so 17 would mean running ourown shared config unsupported.
stylelint-config-prettieris deleted rather thanupgraded — stylelint 15 deprecated its stylistic rules and 16 removed them, so it has
nothing left to turn off. That retirement did not extend to
stylelint-scss, which stillships and enables every rule the old config listed by hand, so those disables stay behind
with a comment explaining why they outlived the package that motivated them.
The commits are ordered so the mechanical churn is separable: the Prettier version bump,
then the repo-wide reformat it causes, then each linter.
Prettier 3 does not fix the MDX corruption, so
*.mdxstays ignoredThe note in
.prettierignoreblamed the corruption on Prettier 2 and said the rule couldgo once we were on 3. That turned out to be wrong, and it seemed worth recording properly
rather than quietly re-adding the line. Prettier 3 has no MDX 3 support either
(prettier/prettier#12209) and still mangles multi-line
{/* … */}comments into{/_ … _/}(prettier/prettier#15163, open since 2023) — output that is not valid MDX, soStorybook fails to index the file.
There is no way to opt out for just those files.
{/* prettier-ignore */}has no effect,and
<!-- prettier-ignore -->does stop Prettier but is not valid MDX 3 — the only syntaxPrettier honours is the one MDX rejects. Since only multi-line comments are affected,
the other 80 files were formatted once by hand and the ignore rule reinstated with the
real reason recorded.
The ESLint upgrade fixed a config bug rather than causing one
eslint-plugin-mdxv1 seteslint-mdxas the parser for every file, not just Markdown.That is the cause of the "eslint isn't merging the overrides as expected" comment this PR
removes, and of
unicorn/no-empty-filereporting four token files whose only statement ismodule.exports. With v3 scoping its parser correctly,.tsfiles pick up@typescript-eslint/parserfrom the shared config on their own, so the workaround is gone.Three config decisions in
eslint.config.mjsare worth a reviewer's attention:settings.n.versionis pinned to our development Node. Without it then/no-unsupported-features/*rules fall back toengines.node, which describes whatconsumers of the published package need — so our gulpfile, Storybook config and tests
were being checked against Node 12. Setting the lint target here deliberately avoids
changing that consumer-facing field as a side effect; Dependency modernization: Node 24, Vite, and Storybook 10 #2391 already notes it deserves
its own decision.
prose.
no-irregular-whitespaceflags the non-breaking spaces indenting the directorytree in CONTRIBUTING.md, and
filename-casewants README.md renamed. Both are off for*.md, whichmdx/remarklints instead.unicorn/prefer-global-thisis off forsrc. Its auto-fix rewriteswindow.setTimeouttoglobalThis.setTimeout, which resolves to Node's overload andreturns a
Timeoutrather than thenumberthese components store — a type error thatlint reported clean and only
tsccaught. In browser-only codewindowis both correctand better typed.
Published output
Everything here is tooling except one line. The Prettier reformat and the ESLint fixes
were both verified to leave
distbyte-for-byte identical —cloudfour-patterns.mjsand.min.jsare unchanged, and the only differences in the unminified UMD bundle are commenttext. Stylelint surfaced three genuine findings, of which only the
place-selfcollapsechanges published CSS, and only into an equivalent declaration; that one carries a patch
changeset.
storybook-mobile, parked alongside the HTML addon during the Storybook migration, staysdropped: it is still at 1.0.1 and peers on React ^17 while Storybook 10 is on 19.
Screenshots
Testing
Run
nvm usefirst — this needs Node 24.19.0.npm ci, thennpm start, and wait for Storybook to open.Controls, Actions, Interactions, Code and Accessibility. Select it — it should show
the story's markup, indented and syntax-highlighted, with a Copy button.
listed in alphabetical order.
-6,with real sizes rather than
0em.nav should render closed with a working toggle button, not stuck open. At a narrow
viewport, the toggle should open and close it with animation.
The form should hide again after a short delay.
render their prose and demos, with no stray
{/_ … _/}text anywhere on the page.1200px. The social icons should sit at the top-right of their area, unchanged from
before this PR.
syntax highlighting.