Skip to content

feat(ci): CSS class consistency build-time gate — fail build on undefined class references #2503

@alexey-pelykh

Description

@alexey-pelykh

Problem

CSS-class drift between ui/src/**/*.{ts,tsx} template-string class references and ui/src/styles/**/*.css rule definitions is currently INVISIBLE to the build pipeline:

  • TypeScript doesn't validate CSS class names (they're opaque strings)
  • oxlint doesn't cross-reference template strings against CSS files
  • Vitest doesn't apply CSS in JSDOM, so unit tests pass with broken styles
  • pnpm check covers format + typecheck + lint — none of which see CSS class drift

This is the same root cause as #2501 (which surfaced two CSS-drift symptoms after the v2026.3.13-1 sync). The audit in <D-1 issue> will find existing drift; this issue adds the build-time gate that prevents recurrence.

Goal

A build-time check, run as part of pnpm check (or equivalently pnpm lint), that fails the build when a fork-owned .ts/.tsx file references a CSS class that has no corresponding rule in any imported CSS file.

Approach options (pick one during implementation)

Option Mechanism Pros Cons
A Standalone scripts/check-css-classes.mjs invoked as a pnpm script, added to pnpm check chain Simple, no plugin development; same script as D-1 audit can be promoted Re-parses CSS every run; no IDE integration
B Custom oxlint rule (if oxlint plugin API supports it) Native lint integration; IDE diagnostics Plugin development complexity; oxlint plugin maturity unclear
C Vite plugin that runs at dev-server time + build time IDE-friendly via Vite HMR; runs on every save Plugin development; only catches errors when dev server runs

Recommendation: Start with A (cheapest, fastest to land). Re-evaluate B/C if developer ergonomics demand.

Implementation plan (Option A)

  1. Promote the audit script from D-1 to be CI-runnable: scripts/check-css-classes.mjs --strict (exit 1 on any finding).
  2. Add pnpm script: "lint:css-classes": "node scripts/check-css-classes.mjs --strict".
  3. Wire into existing pnpm check: extend the check script to include lint:css-classes.
  4. Update CI workflow if it doesn't already run pnpm check (the project CLAUDE.md says it does).
  5. Document in CLAUDE.md § Code Conventions or § Build & Development that CSS class names are checked at build time.

Edge cases to handle

  • Dynamic composition: class=${cond ? "foo" : "bar"} — the script should treat each branch as a class reference.
  • Conditional classes: class="base ${hasAttr ? "modifier" : ""}" — extract base and modifier.
  • External classes: classes that come from third-party CSS (e.g., @create-markdown/preview). Allow opt-in skip via configuration file or inline comment.
  • Compound selectors: a CSS rule .foo .bar defines BOTH .foo and .bar as available — but only as compound. The script should be conservative: count both as defined to minimize false positives.
  • Pseudo-classes/elements: .foo:hover, .foo::before — the class is .foo; the pseudo-suffix doesn't change definition status.

Acceptance criteria

  • pnpm lint:css-classes exists and runs the strict check
  • pnpm check includes lint:css-classes
  • CI build fails when a PR introduces a class reference with no corresponding CSS rule
  • All findings from D-1 (audit) are resolved BEFORE this check is wired into CI (otherwise CI is permanently red)
  • Documented in CLAUDE.md
  • False-positive rate measured at < 5% on the current codebase (after D-1 fixes)

Order of execution

This issue is blocked by:

Once those land, this can be implemented as a follow-up.

Commit message

feat(ci): add CSS class consistency check — fail build on template-string class references with no CSS definition

Estimated effort

2-4 hours depending on chosen mechanism (A < B < C). Recommend starting with A and iterating.

References

  • Bug this prevents recurring: #2501 (CSS class drift after sync)
  • Audit that surfaces existing drift to fix first: companion remoteclaw issue D-1
  • Pattern documentation: remoteclaw/hq#57 (post-mortem), remoteclaw/hq#59 (risk model)
  • Personal Claude skill: fork-sync § Definition-site sync without paired call-site update — Mitigation chore: replace upstream CLAUDE.md with RemoteClaw config #2 (build-time)

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions