Skip to content

fix(lint): surface stylelint results and clear the backlog they hid - #286

Open
rlorenzo wants to merge 5 commits into
mainfrom
fix/stylelint-output-parsing
Open

fix(lint): surface stylelint results and clear the backlog they hid#286
rlorenzo wants to merge 5 commits into
mainfrom
fix/stylelint-output-parsing

Conversation

@rlorenzo

@rlorenzo rlorenzo commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Started as a Windows-only "command line is too long" failure in the CSS lint step. Fixing that revealed the real problem: stylelint has been reporting nothing at all, repo-wide.

Takes VueApp/src from 164 stylelint findings to 0.

Two bugs in the tooling

1. Every stylelint result was being discarded. parseJsonOutput in scripts/lib/lint-staged-common.js read only stdout, but stylelint writes its JSON report to stderr. With stdout empty the function returned [] and the CSS check reported success no matter what it found. The text fallback did not catch it either, since that only fires on a JSON parse error rather than on empty output.

ESLint writes to stdout, so the vue and cshtml checks were unaffected and still report identically.

2. Stylelint received every path as an argument. A whole-tree run passed 207 paths on one command line and blew past the Windows ~8191 character limit. Now batched in groups of 50, matching what runOxfmtCheck already did.

Fixing only the second bug would have left stylelint just as blind, minus the crash. This was caught by injecting a deliberate #FFFFFF violation after the batching fix and finding it still went unreported.

Remediation: 164 to 0

Change Count
Modern color notation, media range syntax, blank lines (stylelint --fix) 21
word-break: break-word to overflow-wrap (one dropped as redundant) 7
Keyframe renamed to kebab-case 1
Vue v-bind() false positive suppressed 1
Keyboard focus rings restored 2
Resolved by moving from the a11y strict tier to recommended 132

The focus ring fix is a real accessibility bug

Two :focus states in StaffDashboard.vue set outline: none and offered only a background tint or a 5% brightness shift in its place, leaving keyboard users with no visible focus indicator (WCAG 2.4.7). Both now carry the system focus ring on :focus-visible, matching the pattern in styles/base.css including the transparent outline that keeps the indicator visible under Windows forced-colors.

One autofix was wrong and was corrected

stylelint --fix generated a @media (prefers-reduced-motion: reduce) block before the rule it overrides. At equal specificity the later declaration wins, so the animation always won and the reduced-motion override never applied. It was also emitted at column 0. Moved after the rule, with a comment recording why order matters. Worth knowing that --fix output needs review rather than being assumed safe; it also wrote three bare-LF lines that had to be normalized back to CRLF.

strict to recommended

The a11y plugin ships two tiers. recommended enables 3 rules, strict adds 9 more.

Lining that up against what was actually found here: both genuine WCAG failures came from recommended rules (no-outline-none, media-prefers-reduced-motion). Every one of the 132 findings from strict-only rules turned out to be a design decision or a false positive:

  • media-prefers-color-scheme (95) asks every colour rule for a dark counterpart. The app ships a single light theme whose pairings are verified against WCAG AA (see DESIGN.md), and Quasar themes via a body--dark class rather than this media query, so even a proper dark mode likely would not satisfy it.
  • no-display-none (13) flags display: none inside @media print and responsive blocks, where it is correct.
  • line-height-is-vertical-rhythmed (12) enforces a baseline grid. This is not WCAG 1.4.12, which requires text to stay readable when a user sets 1.5, not that authors ship it.
  • font-size-is-readable (9) flags 13px on dense UI chrome, a DESIGN.md typography question.

Switching tiers replaces a growing list of one-off rule disables with a single decision, and follows the maintainer's own tiering. The reasoning is recorded in a comment on the extends line.

Verified the plugin is still enforcing rather than silently unloaded: an injected outline: none inside a <style> block is still caught by a11y/no-outline-none.

Dead code

assets/base.css carried a @media (prefers-color-scheme: dark) block from the Vue starter template, flipping --color-* to dark values and implying dark-mode support the app does not have. Removed, along with the seven --vt-c-*-dark declarations it orphaned.

Note for a possible follow-up: that file is reached only by the scaffold main.ts entry, whose App.vue still renders the Vue logo placeholder. None of the seven area SPAs import it, and nothing in web/Views references the main bundle, so the whole index.html -> main.ts -> App.vue -> main.css -> base.css chain looks unused. Removing it would also drop a Vite build target, so it is left out of this PR.

Verification

  • Stylelint: VueApp/src and the Razor CSS both at 0 findings
  • Frontend: 88 files / 1,120 tests pass
  • Backend: 2,708 tests pass
  • Line endings: CRLF preserved throughout, including files stylelint --fix had rewritten

Reviewer note

chore(lint): disable a11y/media-prefers-color-scheme is superseded by the later tier swap, which removed that one-off disable. Kept for history; happy to squash the two if preferred.

Copilot AI review requested due to automatic review settings August 5, 2026 07:39
@codecov-commenter

codecov-commenter commented Aug 5, 2026

Copy link
Copy Markdown

Bundle Report

Changes will decrease total bundle size by 182 bytes (-0.01%) ⬇️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
viper-frontend-esm 2.29MB -182 bytes (-0.01%) ⬇️

Affected Assets, Files, and Routes:

view changes for bundle: viper-frontend-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/schedule-*.js -1 bytes 54.85kB -0.0%
assets/schedule-*.css 3 bytes 9.97kB 0.03%
assets/ManageLinkCollections-*.css -19 bytes 2.02kB -0.93%
assets/SortableList-*.css 104 bytes 1.72kB 6.42% ⚠️
assets/StaffDashboard-*.css 256 bytes 1.71kB 17.62% ⚠️
assets/main-*.css -540 bytes 1.69kB -24.19%
assets/CompetenciesBundleReport-*.css 12 bytes 1.29kB 0.94%
assets/AuditList-*.css 3 bytes 98 bytes 3.16%

Files in assets/schedule-*.js:

  • ./src/ClinicalScheduler/components/ScheduleView.vue → Total Size: 243 bytes

  • ./src/ClinicalScheduler/components/WeekHistoryContent.vue → Total Size: 261 bytes

  • ./src/ClinicalScheduler/components/WeekCell.vue → Total Size: 231 bytes

@codecov-commenter

codecov-commenter commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 50.09%. Comparing base (2242c14) to head (aa2984a).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #286   +/-   ##
=======================================
  Coverage   50.09%   50.09%           
=======================================
  Files         998      998           
  Lines       58370    58370           
  Branches     5858     5858           
=======================================
  Hits        29239    29239           
  Misses      28228    28228           
  Partials      903      903           
Flag Coverage Δ
backend 49.25% <ø> (ø)
frontend 58.45% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes the CSS lint pipeline so Stylelint findings are correctly surfaced (and do not exceed Windows command-line limits), then applies repo-wide style/accessibility remediations to bring Stylelint findings to zero.

Changes:

  • Fix Stylelint JSON parsing to read results from stderr when stdout is empty, and batch file arguments to avoid Windows ENAMETOOLONG.
  • Adjust Stylelint a11y configuration tier (strictrecommended) with documented rationale.
  • Apply stylelint-driven CSS updates across Vue SFCs (wrapping, modern color syntax, media query range syntax, reduced-motion override ordering) and restore visible focus indicators.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
VueApp/src/Effort/pages/StaffDashboard.vue Restores robust keyboard focus indicators using the shared focus-ring pattern.
VueApp/src/Effort/pages/AuditList.vue Replaces word-break with overflow-wrap for better wrapping behavior.
VueApp/src/CTS/pages/CompetenciesBundleReport.vue Updates wrapping rules to overflow-wrap across table cells/columns.
VueApp/src/CTS/components/LevelSelect.vue Modernizes color notation (space-separated rgb() with alpha).
VueApp/src/components/SortableList.vue Ensures reduced-motion override comes after the animated rule so it wins.
VueApp/src/CMS/pages/ManageLinkCollections.vue Refines long-text wrapping rules (remove/replace word-break).
VueApp/src/ClinicalScheduler/components/WeekHistoryContent.vue Suppresses a known Stylelint false positive for Vue SFC v-bind() values.
VueApp/src/ClinicalScheduler/components/WeekCell.vue Renames keyframes to kebab-case and updates the animation reference.
VueApp/src/ClinicalScheduler/components/ScheduleView.vue Updates to media query range syntax for mobile breakpoint styling.
VueApp/src/assets/base.css Removes unused dark-mode starter-template variables and media block.
stylelint.config.mjs Switches a11y tier to recommended with detailed justification.
scripts/lint-staged-css.js Batches Stylelint runs to avoid Windows command-line length limits.
scripts/lib/lint-staged-common.js Fixes JSON parsing to accept Stylelint JSON output from stderr.

Comment on lines +121 to +125
// Status 2 means "violations found" - only warn if no violations were parsed
if (stylelintResult.status === 2) {
const jsonToCheck = stylelintResult.stdout.trim() || stylelintResult.stderr.trim()
const hasValidJson = jsonToCheck && jsonToCheck.startsWith("[")
if (!hasValidJson) {
@rlorenzo
rlorenzo force-pushed the fix/stylelint-output-parsing branch from c07c6ca to bbb97a9 Compare August 5, 2026 13:05
- parseJsonOutput read only stdout, but stylelint writes its JSON report
  to stderr, so every result was discarded and the CSS check always
  reported success regardless of what it found
- stylelint also received every path as an argument, so a whole-tree run
  exceeded the Windows ~8191 char command-line limit and failed outright
- ESLint still writes to stdout, so the vue and cshtml checks are
  unaffected
- VueApp/src now reports 164 issues that were previously invisible
- modern color notation, media range syntax and blank-line spacing, via
  stylelint --fix
- word-break: break-word is a deprecated alias, so switched to
  overflow-wrap; one line was dropped instead, its block already setting
  overflow-wrap: anywhere
- renamed the fadeToBackground keyframe to kebab-case, updating its one
  reference
- suppressed declaration-property-value-no-unknown on Vue's v-bind() in
  CSS, which stylelint cannot resolve
- corrected stylelint's reduced-motion autofix: it emitted the media
  query before the rule it overrides, so at equal specificity the
  animation always won and the override never applied
- takes VueApp/src from 164 stylelint issues to 134, all of which are
  a11y rules needing design decisions
- strict adds nine rules beyond recommended which produced 132 findings
  here and no real defects: dark-theme demands, baseline-grid line
  heights, and display:none inside print and responsive blocks
- both genuine WCAG failures found so far came from recommended rules
- takes VueApp/src and the Razor CSS to zero findings
- both :focus states set outline: none and offered only a background
  tint or a 5% brightness shift in its place, leaving keyboard users
  without a visible focus indicator (WCAG 2.4.7)
- adds the system focus ring on :focus-visible, matching the pattern in
  styles/base.css including the transparent outline that carries the
  indicator under Windows forced-colors
- the block flipped --color-* to dark values under prefers-color-scheme,
  implying dark-mode support the app does not have
- removing it orphaned seven --vt-c-*-dark declarations, dropped too
- base.css reaches only the scaffold main.ts entry; none of the seven
  area SPAs import it
Copilot AI review requested due to automatic review settings August 5, 2026 16:16
@rlorenzo
rlorenzo force-pushed the fix/stylelint-output-parsing branch from bbb97a9 to aa2984a Compare August 5, 2026 16:16
@rlorenzo

rlorenzo commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 59 minutes.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR updates frontend CSS conventions, accessibility behavior, responsive syntax, motion handling, theme variables, and Stylelint processing. Stylelint now uses batched execution and parses reports from either output stream.

Changes

Frontend styling updates

Layer / File(s) Summary
Layout and scheduler styling
VueApp/src/CMS/pages/ManageLinkCollections.vue, VueApp/src/CTS/components/LevelSelect.vue, VueApp/src/CTS/pages/CompetenciesBundleReport.vue, VueApp/src/ClinicalScheduler/components/ScheduleView.vue, VueApp/src/ClinicalScheduler/components/WeekCell.vue, VueApp/src/ClinicalScheduler/components/WeekHistoryContent.vue, VueApp/src/Effort/pages/AuditList.vue
Wrapping declarations, color notation, the mobile media query, animation naming, and a Vue Stylelint suppression are updated.
Focus and motion behavior
VueApp/src/Effort/pages/StaffDashboard.vue, VueApp/src/components/SortableList.vue
Clickable elements now expose focus indicators, and moved-row animation is disabled when reduced motion is preferred.
Light theme variables
VueApp/src/assets/base.css
Dark-mode variables and overrides are removed from the shared stylesheet.

Stylelint workflow

Layer / File(s) Summary
Accessibility lint configuration
stylelint.config.mjs
Stylelint uses the recommended accessibility ruleset and documents excluded strict-tier findings.
Batched lint execution
scripts/lib/lint-staged-common.js, scripts/lint-staged-css.js
CSS files are linted in batches of up to 50. Deprecation warnings are filtered, and JSON output is parsed from stdout or stderr.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • ucdavis/VIPER#174: Related Stylelint configuration and lint-stage processing changes.
  • ucdavis/VIPER#253: Related CMS frontend, accessibility styling, and SortableList changes.

Suggested reviewers: bsedwards

Sequence Diagram(s)

sequenceDiagram
  participant LintStagedCSS
  participant Stylelint
  participant parseJsonOutput
  LintStagedCSS->>Stylelint: lint batches of up to 50 files
  Stylelint-->>LintStagedCSS: return status and output
  LintStagedCSS->>parseJsonOutput: parse cleaned stdout or stderr
  parseJsonOutput-->>LintStagedCSS: return accumulated issues
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: exposing hidden Stylelint results and resolving the reported backlog.
Description check ✅ Passed The description directly explains the Stylelint tooling fixes, findings remediation, accessibility changes, and verification results.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/stylelint-output-parsing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

@rlorenzo

rlorenzo commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
VueApp/src/ClinicalScheduler/components/WeekCell.vue (1)

427-440: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the reduced-motion override win the cascade.

When reduced motion is enabled, the rule at Line [427]-[433] sets animation: none. The base rule at Line [440] appears later with equal specificity. The renamed animation still runs.

Move the reduced-motion block after the base rule. Apply the same ordering to the other reduced-motion transition overrides in this style block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@VueApp/src/ClinicalScheduler/components/WeekCell.vue` around lines 427 - 440,
Reorder the styles in WeekCell.vue so the base
.week-cell__assignment-item--newly-added rule appears before its
prefers-reduced-motion override, allowing animation: none to win the cascade.
Apply the same ordering adjustment to all other reduced-motion transition
overrides in this style block, without changing their declarations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@VueApp/src/components/SortableList.vue`:
- Around line 318-323: Update the reduced-motion comment associated with the
`@media` prefers-reduced-motion rule in SortableList.vue to state that the
.sortable-row--moved animation is disabled, replacing the outdated claim that
the flash still plays. Leave the CSS rule unchanged.

In `@VueApp/src/Effort/pages/StaffDashboard.vue`:
- Around line 1267-1271: Replace the 2px outline width with 0.125rem in both
focus-visible rules, including .dept-row--clickable:focus-visible and the
corresponding rule near the second occurrence, while preserving the existing
box-shadow declarations.

---

Outside diff comments:
In `@VueApp/src/ClinicalScheduler/components/WeekCell.vue`:
- Around line 427-440: Reorder the styles in WeekCell.vue so the base
.week-cell__assignment-item--newly-added rule appears before its
prefers-reduced-motion override, allowing animation: none to win the cascade.
Apply the same ordering adjustment to all other reduced-motion transition
overrides in this style block, without changing their declarations.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 643c668a-1603-43eb-8991-9fd455628654

📥 Commits

Reviewing files that changed from the base of the PR and between 2242c14 and aa2984a.

📒 Files selected for processing (13)
  • VueApp/src/CMS/pages/ManageLinkCollections.vue
  • VueApp/src/CTS/components/LevelSelect.vue
  • VueApp/src/CTS/pages/CompetenciesBundleReport.vue
  • VueApp/src/ClinicalScheduler/components/ScheduleView.vue
  • VueApp/src/ClinicalScheduler/components/WeekCell.vue
  • VueApp/src/ClinicalScheduler/components/WeekHistoryContent.vue
  • VueApp/src/Effort/pages/AuditList.vue
  • VueApp/src/Effort/pages/StaffDashboard.vue
  • VueApp/src/assets/base.css
  • VueApp/src/components/SortableList.vue
  • scripts/lib/lint-staged-common.js
  • scripts/lint-staged-css.js
  • stylelint.config.mjs
💤 Files with no reviewable changes (1)
  • VueApp/src/assets/base.css

Comment on lines +318 to +323
/* Must follow the rule above: equal specificity, so the later declaration wins. */
@media screen and (prefers-reduced-motion: reduce) {
.sortable-row--moved {
animation: none;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the reduced-motion comment.

The comment at Line [299]-[301] says the flash plays under reduced motion. The new rule at Line [320]-[322] disables .sortable-row--moved animation. Update the comment to describe the current behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@VueApp/src/components/SortableList.vue` around lines 318 - 323, Update the
reduced-motion comment associated with the `@media` prefers-reduced-motion rule in
SortableList.vue to state that the .sortable-row--moved animation is disabled,
replacing the outdated claim that the flash still plays. Leave the CSS rule
unchanged.

Comment on lines +1267 to +1271
.dept-row--clickable:focus-visible {
outline: 2px solid transparent;
box-shadow:
0 0 0 0.1rem white,
0 0 0 0.25rem var(--focus-ring-color);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use rem for the focus outline width.

The changed focus-ring declarations use 2px at Line [1268] and Line [1336]. Replace both values with 0.125rem to follow the Vue/CSS sizing guideline.

As per coding guidelines, use rem instead of px for sizing in Vue/CSS.

Proposed fix
-    outline: 2px solid transparent;
+    outline: 0.125rem solid transparent;

Also applies to: 1335-1339

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@VueApp/src/Effort/pages/StaffDashboard.vue` around lines 1267 - 1271, Replace
the 2px outline width with 0.125rem in both focus-visible rules, including
.dept-row--clickable:focus-visible and the corresponding rule near the second
occurrence, while preserving the existing box-shadow declarations.

Source: Coding guidelines

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants