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
Summary
Accessibility improvements for the CTS (Competency Tracking System) area — PR 3 of 6 in the VPR-104 accessibility audit.
Landmarks, Structure & Headings
<h1>; 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-label+aria-expandedon the global expand-all button and each per-EPA expand button; label adapts with state ("Expand details for…" / "Collapse details for…")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))AssessmentBubblerating redesign — the rating circles inMyAssessmentsbecome<button>elements with the numeric rating inside, uniform UC Davis blue palette (blue-70→blue-100), bold white text, and a goldfocus-visiblering. Replaces the old low-contrast light blue (rgba(62,127,238,0.3)) fill and provides a non-color indicator so colorblind users can still read the rating (1.4.1 Use of Color, 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