VPR-104 fix(a11y): CTS area accessibility improvements (PR 3 of 6)#137
VPR-104 fix(a11y): CTS area accessibility improvements (PR 3 of 6)#137
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves accessibility in the CTS area by correcting heading hierarchy, adding focus management on route changes, and improving labeling for icon-only controls across both Razor and Vue CTS pages.
Changes:
- Promotes page headings to
<h1>across CTS Razor + Vue pages and adds missing primary headings where needed. - Adds route-change focus management via a new
useRouteFocuscomposable wired into the CTS Vue router. - Adds accessible labels (
aria-label/label) for icon-only edit/actions and form inputs; includes some strict-equality cleanups in Razor-embedded Vue code.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| web/Areas/CTS/Views/ManageLevels.cshtml | Updates heading to h1, adds aria-label to edit button, strict equality cleanup in addLevel. |
| web/Areas/CTS/Views/ManageEpas.cshtml | Adds missing h1 and aria-label to edit button. |
| web/Areas/CTS/Views/ManageDomains.cshtml | Updates heading to h1, improves input labeling, moves “Domains” heading outside list header, adds aria-label to edit link, formatting cleanup. |
| web/Areas/CTS/Views/Index.cshtml | Updates heading to h1. |
| web/Areas/CTS/Views/EpaEdit.cshtml | Adds h1 heading. |
| web/Areas/CTS/Views/Epa.cshtml | Adds h1 heading; strict equality and small code-quality updates in embedded JS. |
| web/Areas/CTS/Views/Assessments.cshtml | Updates heading to h1; minor JS cleanup (destructuring/strict null check). |
| VueApp/src/CTS/router/index.ts | Hooks up useRouteFocus(router) for accessibility focus management on navigation. |
| VueApp/src/CTS/pages/MyAssessmentCharts.vue | Updates heading to h1. |
| VueApp/src/CTS/pages/ManageSessionCompetencies.vue | Updates heading to h1. |
| VueApp/src/CTS/pages/ManageRoles.vue | Updates heading to h1 and adds aria-label for edit button. |
| VueApp/src/CTS/pages/ManageMilestones.vue | Updates heading to h1. |
| VueApp/src/CTS/pages/ManageLevels.vue | Updates heading to h1 and adds aria-label for edit button. |
| VueApp/src/CTS/pages/ManageEpas.vue | Updates form heading to h1 and adds aria-label for edit button. |
| VueApp/src/CTS/pages/ManageDomains.vue | Updates heading to h1 and adds aria-label for edit button. |
| VueApp/src/CTS/pages/ManageCompetencies.vue | Updates heading to h1. |
| VueApp/src/CTS/pages/ManageBundles.vue | Updates heading to h1; adds aria-labels for edit and “view competencies” buttons. |
| VueApp/src/CTS/pages/ManageBundleCompetencies.vue | Updates heading to h1. |
| VueApp/src/CTS/pages/CtsHome.vue | Updates heading to h1. |
| VueApp/src/CTS/pages/CourseStudents.vue | Updates heading to h1. |
| VueApp/src/CTS/pages/AuditList.vue | Updates heading to h1. |
| VueApp/src/CTS/pages/AssessmentList.vue | Updates heading to h1. |
| VueApp/src/CTS/pages/AssessmentEpaEdit.vue | Adds alt text for student photo. |
| VueApp/src/CTS/pages/AssessmentCompetency.vue | Updates heading to h1. |
2dc345b to
901cec9
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
web/Areas/CTS/Views/Index.cshtml:9
<script>tag usesasp-asp-add-nonce(typo) instead of the establishedasp-add-nonce. This prevents the CSP nonce TagHelper from running and can break CSP for inline scripts in this view; rename the attribute (or remove the empty script tag if it’s not needed).
<h1>CTS Home</h1>
@if (html != null) { Html.Raw(html); }
<script asp-asp-add-nonce="true">
web/Areas/CTS/Views/Epa.cshtml:243
getStudentsuses strict inequality againststudentsLoadedForService. Sinceservicecan be set from localStorage (string) and from API defaults (number),this.service !== this.studentsLoadedForServicecan stay true even when the same service is selected, causing repeated schedule fetches. Consider coercing both to numbers (or both to strings) before comparing.
if (this.service && this.service !== this.studentsLoadedForService) {
var d = new Date().toJSON().split("T")[0]
await viperFetch(this, "../api/cts/clinicalschedule/student?serviceId=" + this.service + "&startDate=" + d + "&endDate=" + d)
.then(data => this.studentsOnService = data.map(s => ({
label: s.lastName + ", " + s.firstName, mailId: s.mailId, value: s.personId
})))
this.studentsLoadedForService = this.service
}
|
@bsedwards Some bugs were fixed in the automated review that you might want to double-check.
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 24 changed files in this pull request and generated 9 comments.
Comments suppressed due to low confidence (1)
web/Areas/CTS/Views/Epa.cshtml:242
this.servicecan be a string (from storage) or a number (from auto-select / UI), and switching to strict!==can cause unnecessary re-fetching when values are equal but types differ (e.g., "5" vs 5). Consider normalizingservice/studentsLoadedForServiceto a single type (number) before comparing and assigning.
if (this.service && this.service !== this.studentsLoadedForService) {
var d = new Date().toJSON().split("T")[0]
await viperFetch(this, "../api/cts/clinicalschedule/student?serviceId=" + this.service + "&startDate=" + d + "&endDate=" + d)
.then(data => this.studentsOnService = data.map(s => ({
label: s.lastName + ", " + s.firstName, mailId: s.mailId, value: s.personId
})))
this.studentsLoadedForService = this.service
177ffdc to
8555e5b
Compare
8555e5b to
cedea92
Compare
899e4bf to
1c8f7a6
Compare
2b59ac1 to
99f2309
Compare
99f2309 to
f586754
Compare
- Fix heading hierarchy: h2 → h1 on all 16 Vue pages, add h1 to Razor views - Add route focus management via useRouteFocus composable (C4) - Add aria-label to icon-only edit buttons on ManageDomains, ManageEpas, ManageLevels, ManageBundles, ManageRoles (Vue + Razor) - Add aria-label to bundle competencies link buttons - Fix ManageDomains.cshtml: add label props to inputs, move h3 outside q-list - Add alt text to student photo in AssessmentEpaEdit - Strict equality fixes and code quality cleanup in Razor views
- Fix milestone type case mismatch in ManageLevels.cshtml ("milestone" → "Milestone")
- Fix serviceID → serviceId casing in Epa.cshtml (pre-existing bug, confirmed via API)
- Fix asp-asp-add-nonce typo → asp-add-nonce in Index.cshtml
- Move ManageEpas h1 outside v-if so page always has a heading
- Fix service check in Epa.cshtml getEpas: != to !== change caused 0 !== '' to evaluate true, use > 0 for correct numeric check
- Include item name in aria-label for edit buttons in list views - Applies to ManageLevels, ManageEpas, ManageDomains, ManageRoles, and ManageBundles (both .cshtml and .vue versions) - Makes controls distinguishable for screen reader users (WCAG 2.4.6)
…d palette - Replace color="green" with color="positive" (Redwood) on ~15 add/create buttons - Replace color="red"/"red-5"/"red-7" with color="negative" (Merlot) on ~12 delete buttons - Replace color="blue" with color="primary" on Last Week badges - Add text-color="dark" to 3 info-colored chips/buttons for AA contrast - Fix duplicate text-color attributes in CompetenciesBundleReport - Remove redundant global comments and unused function parameters
…to CTS - Add X close button with aria-label="Close dialog" to 4 CTS dialogs (ManageBundleCompetencies, ManageCompetencies, ManageSessionCompetencies, MyAssessments) - Add $q.dialog() delete confirmation to 8 CTS delete functions (ManageDomains, ManageEpas, ManageCompetencies, ManageBundles, ManageRoles, ManageLevels, ManageBundleCompetencies x2)
- Replace hardcoded blue (#1E88E5) with var(--q-secondary) in LevelSelect for WCAG AA contrast compliance (~7.4:1) - Replace non-existent q-label with div in ManageDomains - Fix Promise.resolve → Promise.all in ManageCompetencies load() and add null guard for failed hierarchy API - Migrate q-banner to StatusBanner in AssessmentEpa/AssessmentEpaEdit - Fix ManageRoles 400 error: initialize role with roleId: 0 to satisfy required int on RoleDto - Fix ManageMilestones 400 error: add missing competencyCount to bundle payload
- Add label props to ManageDomains q-inputs for screen reader accessibility (axe: label violation, WCAG 4.1.2) - Show "No competencies found" text instead of empty q-tree to avoid aria-required-children and grey contrast failures on empty state
…hildren The h3 "Domains" heading was a direct child of q-list (role="list"), violating WCAG 1.3.1 — list elements must only contain listitem children. Move heading above q-list so the role hierarchy is valid.
- Add ViewData["Title"] to all CTS Razor views - Replace Quasar palette colors with brand colors (positive/negative) - Use secondary color for Cancel buttons - Add accessible labels to empty table columns and tree buttons - Fix Promise.resolve → Promise.all in ManageMilestones - Change CompetencyHierarchyDto.Children to List for Add() support - Refactor AuditList detail toggle out of table header - Add Cancel button and clearLevel to ManageLevels - Fix stored term lookup in ManageCourseCompetencies
- Rewrite AssessmentBubble as semantic button/span with visible numeric rating and aria-label instead of an icon-only q-icon - Replace raw red error divs with StatusBanner across Assessment* pages; add v-model:visible support so dismissible banners reset - Fix MyAssessments heading hierarchy (h1/h2) and label expand toggles with aria-expanded - Add missing aria-labels and select label on CourseStudents - Fix ManageLevels add/edit form visibility toggle
a712ebe to
66d60b9
Compare
- AssessmentBubble aria-label reads the rating name (e.g. "Trust with indirect supervision") instead of "Rating N of 5" so low ratings are not announced to students; tests lock in the contract. - MyAssessments: realign expanded details under the EPA name, move toggles to the left edge, widen the comment, and show the rating pill (instead of the bubble+label pair) in the details modal. - LevelSelect paints the selected level in its rating color so sighted users get the same feedback the bubble list conveys. - AssessmentEpaEdit promotes the EPA title to h1 to fix heading order; AssessmentList search input gains a visible outline.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #137 +/- ##
==========================================
- Coverage 42.66% 42.63% -0.04%
==========================================
Files 802 833 +31
Lines 48449 48585 +136
Branches 4446 4461 +15
==========================================
+ Hits 20672 20712 +40
- Misses 27286 27382 +96
Partials 491 491
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
Bundle ReportChanges will increase total bundle size by 8.48kB (0.37%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: viper-frontend-esmAssets Changed:
Files in
Files in
Files in
Files in
Files in
Files in
Files in
Files in
Files in
Files in
Files in
Files in
Files in
Files in
Files in
Files in
Files in
Files in
Files in
Files in
|
…e branch Restore MyAssessments.vue, AssessmentBubble.vue, cts.css, and remove the new assessment-bubble.test.ts. These changes move to VPR-104-accessibility-audit-CTS-myassessments for a separate PR.
|
@bsedwards, I separated the changes to the MyAssessment page into a separate branch (#155) so that this branch, with all the other accessibility changes, can get approved and pushed out. |
Summary
Accessibility improvements for the CTS (Competency Tracking System) area — PR 3 of 6 in the VPR-104 accessibility audit. MyAssessments-scoped changes and the
AssessmentBubblecomponent rework are split into #155.Landmarks, Structure & Headings
<h1>(includingAssessmentEpaEdit); demote secondary headings to<h2>; add<h1>to CTS Razor views (Index,Assessments,Epa,EpaEdit,ManageDomains,ManageEpas,ManageLevels) (1.3.1 Info and Relationships)q-listso it no longer breaks thearia-required-childrenruledocument.titleon every CTS route via router meta;ViewData["Title"]on all CTS Razor views (AssessmentCharts, Assessments, Epa, EpaEdit, Index, ManageDomains, ManageEpas, ManageLevels) (2.4.2 Page Titled)useRouteFocusinto the CTS router so SPA page changes announce correctly to screen readers (2.4.3 Focus Order)ViperLayoutSimplesetsinheritAttrs: falseto silence the Vue fragment-root warning whenApp.vuepassesnav/navarea/highlighted-top-navto a layout that doesn't consume themcol-12to the<h3>so the flex stack renders correctlyLabels for Icon-only Buttons & Form Controls
aria-labelon edit buttons acrossManageDomains,ManageEpas,ManageLevels,ManageBundles,ManageRoles,ManageCompetenciestree, and bundle competency links; labels include the item name so screen-reader users hear "Edit level: Level 1" instead of a row of identical "Edit" buttons (4.1.2 Name, Role, Value)aria-labelon everyassignmentanddeleteicon button, named per student (e.g., "Assess competency for Xander Avila" / "Remove Xander Avila"); "Manually added" label on the check iconlabel="Student"on theCourseStudentsq-select; label props added to previously unlabeled inputs inManageDomains.cshtml(3.3.2 Labels or Instructions)Dialogs, Confirmations & Status Messages
aria-label="Close dialog") on CTS Razor dialogs (Epa.cshtml,EpaEdit.cshtml,Assessments.cshtml) (2.1.1 Keyboard)$q.dialog()confirmation on delete actions inManageDomains,ManageEpas,ManageLevels,ManageRoles,ManageBundles,ManageCompetencies,ManageBundleCompetencies,ManageSessionCompetencies(Razor + Vue) (3.3.4 Error Prevention)StatusBannerreuse —StatusBannernow exposesv-model:visibleso dismissed banners reopen on subsequent saves;AssessmentEpaEditbindsv-model:visible="success"to fix a regression where the "EPA Saved" banner stayed hidden after re-saving. Close button repositioned inline viainline-actions(4.1.3 Status Messages)bg-red-5 text-whiteerror blocks onAssessmentEpa,AssessmentEpaEdit, andAssessmentCompetencynow render via<StatusBanner type="error">, inheriting the accessible tinted background androle="alert"from the shared componentColor & Contrast
color="green"/"red"/"red-5"replaced with brand tokens (positive/negative) on add/create/delete buttons; success banners switched frombg-green text-whitetobg-positive text-white; "This Week" badge fromgreen→positive; Cancel buttons switched fromprimarytosecondaryto reduce visual weight (1.4.3 Contrast (Minimum))LevelSelectcolored selection — the selected level button now paints in the same rating palette used byAssessmentBubble(see CTS - MyAssessment UI changes #155), so sighted users get the matching visual feedback they see elsewhere on the page instead of a generic "selected" stateAssessmentListsearch outline — the search input switches fromborderlesstooutlinedwith a white background so the control boundary is visible against the table header (1.4.11 Non-text Contrast)Table Semantics & Layout
AssessmentList,ManageBundles,ManageBundleCompetencies,Assessments.cshtml,AuditList(1.3.1 Info and Relationships)AuditListDetails toggle — move the "Details" toggle out of the column header into the toolbar; use computed columns so the Details column only exists when the toggle is onCourseStudentscolumn alignment — center the "Assess Competency" and "Remove" action columns (header + cells) so the icons no longer left-align out of the headerImages
AssessmentEpaEdit(1.1.1 Non-text Content)Bug Fixes (pre-existing)
ManageLevels.cshtml's "Delete Level" existed since the original commit but had no click handler or delete method. Wired it toDELETE /api/cts/levels/{levelId}with a confirmation dialog; button now only appears when editing an existing level.ManageLevelsCancel button didn't hide the form — the Cancel button was added alongsideclearLevel(), but the form'sv-ifgated onObject.keys(level).length(always truthy sinceemptyLevelhas ten keys). Gate switched toshowFormso Cancel actually dismisses the form.ManageMilestonesparallel load was sequential —Promise.resolve([...])should have beenPromise.all([...]); the three API calls were started but never awaited correctly and could race with theloadedflag.CompetencyHierarchyDto.Childrentyping mismatch — declaredIEnumerable<CompetencyHierarchyDto>but the controller cast it toList<>to call.Add(). Declaration changed toList<>and the cast removed.ManageCourseCompetenciesstored term lookup — the stored term fromlocalStoragewas used as-is even when stale; now looks up the matching term bytermCodein the current term list and falls back to the latest term if not found.Code Quality
==/!=→===/!==) in Razor views