You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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)
Promote the audit script from D-1 to be CI-runnable: scripts/check-css-classes.mjs --strict (exit 1 on any finding).
Wire into existing pnpm check: extend the check script to include lint:css-classes.
Update CI workflow if it doesn't already run pnpm check (the project CLAUDE.md says it does).
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:
<D-1 issue> — audit must complete first to surface and fix existing drift
Problem
CSS-class drift between
ui/src/**/*.{ts,tsx}template-string class references andui/src/styles/**/*.cssrule definitions is currently INVISIBLE to the build pipeline:pnpm checkcovers format + typecheck + lint — none of which see CSS class driftThis 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 equivalentlypnpm lint), that fails the build when a fork-owned.ts/.tsxfile references a CSS class that has no corresponding rule in any imported CSS file.Approach options (pick one during implementation)
scripts/check-css-classes.mjsinvoked as apnpmscript, added topnpm checkchainRecommendation: Start with A (cheapest, fastest to land). Re-evaluate B/C if developer ergonomics demand.
Implementation plan (Option A)
scripts/check-css-classes.mjs --strict(exit 1 on any finding).pnpmscript:"lint:css-classes": "node scripts/check-css-classes.mjs --strict".pnpm check: extend thecheckscript to includelint:css-classes.pnpm check(the project CLAUDE.md says it does).Edge cases to handle
class=${cond ? "foo" : "bar"}— the script should treat each branch as a class reference.class="base ${hasAttr ? "modifier" : ""}"— extractbaseandmodifier.@create-markdown/preview). Allow opt-in skip via configuration file or inline comment..foo .bardefines BOTH.fooand.baras available — but only as compound. The script should be conservative: count both as defined to minimize false positives..foo:hover,.foo::before— the class is.foo; the pseudo-suffix doesn't change definition status.Acceptance criteria
pnpm lint:css-classesexists and runs the strict checkpnpm checkincludeslint:css-classesOrder of execution
This issue is blocked by:
<D-1 issue>— audit must complete first to surface and fix existing drift#2501— must merge first (otherwise this check would block fix(ui): restore nav-section/topbar class names after v2026.3.13-1 sync — paired call-site update missed #2501's PR)Once those land, this can be implemented as a follow-up.
Commit message
Estimated effort
2-4 hours depending on chosen mechanism (A < B < C). Recommend starting with A and iterating.
References
#2501(CSS class drift after sync)remoteclaw/hq#57(post-mortem),remoteclaw/hq#59(risk model)fork-sync § Definition-site sync without paired call-site update— Mitigation chore: replace upstream CLAUDE.md with RemoteClaw config #2 (build-time)