Skip to content

fix(styles): remediate all stylelint violations and gate token usage in CI#1847

Merged
steilerDev merged 1 commit into
betafrom
fix/1815-stylelint-enforcement
Jul 7, 2026
Merged

fix(styles): remediate all stylelint violations and gate token usage in CI#1847
steilerDev merged 1 commit into
betafrom
fix/1815-stylelint-enforcement

Conversation

@steilerDev

Copy link
Copy Markdown
Owner

Summary

  • Stylelint was defined (.stylelintrc.json + npm run stylelint script) but never invoked anywhere — not in npm run lint, not in CI. This left 370 latent violations across client/src/**/*.css and *.module.css.
  • Fixes the missing color-function-alias-notation: null override for tokens.css (resolves 116 violations there alone), extracts ~20 new semantic tokens 1:1 from previously-hardcoded values (zero visual change), and remediates every remaining violation: deprecated clip/page-break-* properties, :not() chain modernization, font-weight/z-index literals → scale tokens, duplicate selectors, and one inline style moved to a CSS Module class.
  • Real bug fix included: InvoiceBudgetLinesSection.module.css used @extend .td; (SCSS syntax) six times inside a plain CSS Module — this project has no Sass, so it was a silent no-op and six table cells never received their intended padding. Replaced with the actual declaration.
  • Wires stylelint into npm run lint/lint:fix and adds a dedicated Stylelint step to the CI static-analysis job. Deliberately does not route through npm run lint in CI, since ESLint has 8 pre-existing errors / 50 warnings out of scope for this PR that would otherwise break CI immediately — that cleanup is left for a follow-up issue.

Fixes #1815

Test plan

  • npx stylelint "client/src/**/*.css" "client/src/**/*.module.css" — 0 errors, 0 warnings (independently re-verified during review)
  • Full unit/integration suite for all touched components — 29 suites / 1371 tests green
  • npm run stylelint (as CI will invoke it) — clean
  • ESLint baseline unchanged (8 errors / 50 warnings, confirmed identical before/after)
  • CI Quality Gates (including the new Stylelint step, running for the first time on this PR)

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) noreply@anthropic.com

…in CI

Stylelint was defined but never executed anywhere (not in npm run lint,
not in CI), leaving 370 latent violations across client/src/**/*.css and
*.module.css. This fixes all of them and wires stylelint into both local
and CI gates so the rules actually enforce design-token usage going
forward, as CLAUDE.md already claimed they did.

- Add missing `color-function-alias-notation: null` to the tokens.css
  stylelint override (resolves 116 violations in tokens.css alone).
- Add ~20 new semantic tokens (--z-raised, --color-warning-badge-bg,
  --color-tooltip-*, --color-photo-*, --shadow-text-overlay) extracted
  1:1 from previously-hardcoded values — no visual changes.
- Fix a real bug found during remediation: InvoiceBudgetLinesSection
  used `@extend .td;` (SCSS syntax) six times in a plain CSS Module,
  which is a silent no-op — six table cells were never receiving their
  intended padding. Replaced with the actual declaration.
- Remove dead/duplicate CSS selectors (HouseholdItemDetailPage had a
  legacy pre-token block fully shadowed by a later tokenized
  reimplementation of the same selectors; CostBreakdownTable had two
  more).
- Replace deprecated `clip`/`page-break-*` properties, modernize
  `:not()` chains, replace hardcoded font-weight/z-index literals with
  scale tokens, and move an inline style to a CSS Module class.
- Chain stylelint into `npm run lint`/`lint:fix`; add a dedicated
  `Stylelint` step to the CI `static-analysis` job (intentionally not
  `npm run lint`, since ESLint's pre-existing 8 errors/50 warnings are
  out of scope for this PR and would break CI immediately).

Fixes #1815

Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>
@steilerDev

Copy link
Copy Markdown
Owner Author

[security-engineer] Security review of PR #1847 (fix/1815-stylelint-enforcement)

Scope reviewed: git diff origin/beta..HEAD (54 files) — CSS token remediation across ~50 .module.css/print.css files, one CSS custom-property addition in tokens.css, one TSX inline-style-to-class swap, .stylelintrc.json rule addition, a new CI step in .github/workflows/ci.yml, package.json script chaining, and a CLAUDE.md doc note.

CI workflow change (.github/workflows/ci.yml):

  • Adds a Stylelint step (run: npm run stylelint) inside the existing static-analysis job, positioned after Type check and before Build.
  • No new GitHub Actions/third-party actions introduced — only an existing npm script.
  • No new permissions, secrets, or environment variables referenced by the step.
  • Command runs against tracked, glob-scoped source paths (client/src/**/*.css, client/src/**/*.module.css per the stylelint script in package.json) — no user-controlled or PR-provided input reaches the shell command, so no script/command injection surface (e.g., no ${{ github.event.* }} interpolation in the run block).

package.json: lint/lint:fix now chain && npm run stylelint[:fix]. No new dependencies added, no lockfile changes observed — stylelint was already a dependency.

CSS/token changes: All ~50 CSS diffs replace hardcoded hex/rgba/z-index/font-weight literals with var(--token) references already defined in tokens.css, plus two :not(a):not(b):not(a, b) selector modernizations and clip: rect(0,0,0,0)clip-path: inset(50%) (equivalent visually-hidden technique, no behavior change). All values are static and sourced from the same design-token file — no user input reaches CSS, no content-injection vector.

TSX change (HouseholdItemDetailPage.tsx): Replaces style={{ marginTop: '0.5rem' }} with a static CSS Module class styles.errorBannerSpacingTop, applied via template-literal className concatenation of two fixed class names (styles.errorBanner, styles.errorBannerSpacingTop). Neither value is derived from user/API-controlled input — no XSS or DOM-injection vector introduced or removed.

Findings: None. No SQL/command/XSS injection vectors, no auth/authorization surface touched, no new dependencies, no secrets exposure, no CI script-injection pattern.

VERDICT: APPROVED

@steilerDev

Copy link
Copy Markdown
Owner Author

@/tmp/pr1847-review.md

@steilerDev

Copy link
Copy Markdown
Owner Author

[product-architect] Architecture review — APPROVED (2 medium notes for follow-up, non-blocking)

Reviewed git diff origin/beta..HEAD (57 files; CI, stylelint config, tokens, CLAUDE.md, and the CSS remediation sweep). Verified against the review scope.

Verified sound

CI gating design — Correct sequencing. The Stylelint step is added to the static-analysis job (part of the required Quality Gates workflow) and runs the scoped npm run stylelint glob rather than npm run lint. This deliberately gates token usage now while keeping the deferred ~8-error eslint debt out of CI. lint/lint:fix chain stylelint locally, so devs still catch violations pre-push. The if: app == 'true' change filter covers client/, so CSS-only PRs trigger the job. The known follow-up (eslint CI gating once the debt is paid) is the right next step.

Token-system additions — Good architectural fit:

  • --z-raised: 1 is a well-commented semantic addition to the z-scale, cleanly replacing raw z-index: 1.
  • Theme-invariant --color-photo-* tokens are correctly defined only in :root (not overridden in dark) — photo overlays sit on a dark surface regardless of app theme. Naming is clear.
  • Gantt tooltip tokens are correctly promoted from component-local custom props to global tokens defined in both themes.
  • print.css retains raw hex behind a scoped stylelint-disable color-no-hex with a justification (print reset anchors to Layer 1 palette constants, independent of the theme system) — legitimate escape hatch, CI stays green.

@extend bug fix — Correct. .td defines only padding: var(--spacing-3), so replacing the silently-ignored @extend .td; with an explicit padding: var(--spacing-3); exactly reproduces the intended cascade — and restores padding these cells never actually received (genuine latent bug, not cosmetic). .tdLinkedItem correctly just drops the dead @extend since it already carries its own padding. Duplicate-selector merges (e.g. .trRemaining_warning .tdRemaining) are equivalence-preserving.

CLAUDE.md accuracy — Claims now match reality: job name static-analysis, a Stylelint step, glob client/src/**/*.css + **/*.module.css, excludes docs/, and runs via npm run lint locally. All accurate. No schema/API-contract surface touched.

Medium findings (recommend follow-up, not blocking)

M1 — Undocumented --z-local escape-hatch convention. PhotoMetadataSidepanel.module.css introduces local --z-local: 8 / --z-local: 20 custom properties whose purpose is to hold literal z-index values that pass the new stylelint numeric z-index disallow-list (it only inspects literal values, not var()). This is a reasonable pattern for a component-local stacking context that shouldn't pollute the global z-scale — but it is undocumented and effectively bypasses the enforcement this PR adds. Per CLAUDE.md's Cross-Team Convention rule, this new convention should be captured (a comment in the tokens.css z-scale section and/or a Style Guide note distinguishing "global --z-* scale tokens vs component-local --z-local for isolated stacking contexts"), so future devs know when each applies and don't reach for --z-local to sidestep the scale. Suggest a small follow-up.

M2 — HouseholdItemDetailPage .modalContent/.modalTitle dedup is not value-identical. The two duplicate .modalContent blocks had diverged: the removed one carried position: relative, width: calc(100% - var(--spacing-8)), margin: var(--spacing-4), box-shadow: var(--shadow-2xl), max-width: 28rem; the surviving one has box-shadow: var(--shadow-lg), max-width: 90vw, and no width/margin/position. With equal specificity the pre-fix computed style was the cascade union, so removing one drops position: relative/width/margin and changes the shadow + max-width; .modalTitle weight also shifts semibold→bold. The overlay centers via flexbox and the only position: absolute descendant (.searchDropdown) is not inside the modal, so nothing breaks structurally — but the delete-confirmation modal's width/shadow will visibly change. This is framed as a no-op "duplicate selector" removal but is a genuine computed-style change; recommend ux-designer + E2E confirm the modal still renders as intended.

Neither finding blocks merge to beta — both are appropriately caught by downstream UX/E2E review. Nice, disciplined sweep overall.

@steilerDev steilerDev merged commit 7756e7b into beta Jul 7, 2026
27 of 32 checks passed
@steilerDev steilerDev deleted the fix/1815-stylelint-enforcement branch July 7, 2026 20:21
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.13.0-beta.16 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant