mittwald / mittwald/flow

Nothing verifies that :global(.flow--…) selectors name a class that exists

Open
#3,095 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

tooling
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 is flow--column-layout, not flow--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-layout matches flow--column-layout-container and the bug hides.
  • Skip .md files, or the prose ellipsis in AGENTS.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 during nx dev.
  • A unit test fits the existing wiring. test:unit already has dependsOn: ["^build"], so dist/css/all.css exists 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.