Nothing verifies that :global(.flow--…) selectors name a class that exists
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 15
- Forks
- 3
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 184
Description
A :global(.flow--…) selector that names a class nothing defines fails completely silently — no lint error, no build error, no test failure. Nothing verifies these names resolve.
main is currently clean: all 39 real :global(.flow--…) references resolve against the built CSS. This is a preventive guard, not a repair.
How it bites
A :global() selector has no compile-time link to the module that owns the class, so nothing connects the two:
- The generated names only exist after the Vite CSS-modules transform, so no source-level tool can see them.
- The generator drops the suffix when it equals the component name —
ColumnLayout's inner class isflow--column-layout, notflow--column-layout--column-layout. Writing the doubled form is the natural guess and it matches nothing. - The CSS stays valid, so the rule is simply inert.
Caught in practice during #3021: ListItemView was changed from .columnLayout > div to :global(.flow--column-layout--column-layout), silently lost align-items: center, and a heading sat 12 px off. Only the visual suite noticed, and only because that scenario happened to have a baseline.
The check
Verified working — this is what found the case above:
CSS=packages/components/dist/css/all.css # after: pnpm nx build components
grep -rho ':global(\.flow--[a-z0-9-]*' packages/components/src apps/docs/src \
| sed 's/:global(\.//' | sort -u \
| while read -r n; do grep -q "\.$n[^a-z0-9-]" "$CSS" || echo "MISSING $n"; done
Two refinements a real gate needs:
- The trailing
[^a-z0-9-]guard is load-bearing. Without it,flow--column-layoutmatchesflow--column-layout-containerand the bug hides. - Skip
.mdfiles, or the prose ellipsis inAGENTS.md/PATTERNS.md(:global(.flow--…)written as an example) reports as a false positive.
Where it belongs: a unit test in components
- Not a stylelint plugin. Stylelint sees one stylesheet at a time and cannot know what classes other modules define; the names do not exist until the CSS-modules transform has run. It would have to shell out to a build, which is not a linter's job.
- Not a build-time assertion. Wrong failure moment — it would break
pnpm build, which is also what produces the artifact being checked, and it would fire duringnx dev. - A unit test fits the existing wiring.
test:unitalready hasdependsOn: ["^build"], sodist/css/all.cssexists when it runs.
One nx caveat, per the Common failures table: dist/** is gitignored and therefore cannot be an inputs glob — nx hashes only files in its workspace file map. The target needs { "dependentTasksOutputFiles": "**/*", "transitive": false }, or nx serves a stale cached pass when the CSS changes.
Scope — two things this does not cover
It generalises usefully past :global(): the same silent-miss class covers styles["flow--…"] keys in .module.scss and hardcoded flow-- strings in TSX. Worth including.
But it only proves a class is defined somewhere — not that the selector matches anything at runtime. The dead hover carve-out in #3086 is a different failure: a name that resolves against a different module than the element it targets, so the rule is inert despite every name existing. Catching that needs a rendered-DOM check, not a name check. One gate will not cover both; do not expect it to.
Provenance
Found while triaging #3021, where the check was built ad hoc to verify that refactor and caught a real defect in it.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the components unit-test target and its Nx configuration, then inspect packages/components/dist/css/all.css after pnpm nx build components. Add a gate that checks the referenced flow-- names from the stated source files and covers the listed styles and TSX cases, while excluding Markdown and preserving exact class-boundary matching. Done means the unit test fails for a missing class and avoids stale cached passes when the CSS output changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, scss, typescript, vite
- Domain
- build-system, frontend, testing
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100