Skip to content

VPR-104 fix(a11y): CTS area accessibility improvements (PR 3 of 6)#137

Open
rlorenzo wants to merge 11 commits intomainfrom
VPR-104-accessibility-audit-CTS
Open

VPR-104 fix(a11y): CTS area accessibility improvements (PR 3 of 6)#137
rlorenzo wants to merge 11 commits intomainfrom
VPR-104-accessibility-audit-CTS

Conversation

@rlorenzo
Copy link
Copy Markdown
Contributor

@rlorenzo rlorenzo commented Apr 1, 2026

Summary

Accessibility improvements for the CTS (Competency Tracking System) area — PR 3 of 6 in the VPR-104 accessibility audit.

Landmarks, Structure & Headings

  • Heading hierarchy — promote the top heading on every CTS Vue page and Razor view to <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)
  • List semantics — move a stray heading out of a q-list so it no longer breaks the aria-required-children rule
  • Document titles — unique document.title on 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)
  • Route focus management — wire useRouteFocus into the CTS router so SPA page changes announce correctly to screen readers (2.4.3 Focus Order)
  • Layout warningViperLayoutSimple sets inheritAttrs: false to silence the Vue fragment-root warning when App.vue passes nav / navarea / highlighted-top-nav to a layout that doesn't consume them
  • Domains row layout — add col-12 to the <h3> so the flex stack renders correctly

Labels for Icon-only Buttons & Form Controls

  • Unique edit-button labelsaria-label on edit buttons across ManageDomains, ManageEpas, ManageLevels, ManageBundles, ManageRoles, ManageCompetencies tree, 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)
  • MyAssessments expand/collapsearia-label + aria-expanded on the global expand-all button and each per-EPA expand button; label adapts with state ("Expand details for…" / "Collapse details for…")
  • CourseStudents action buttonsaria-label on every assignment and delete icon button, named per student (e.g., "Assess competency for Xander Avila" / "Remove Xander Avila"); "Manually added" label on the check icon
  • Unlabeled form inputslabel="Student" on the CourseStudents q-select; label props added to previously unlabeled inputs in ManageDomains.cshtml (3.3.2 Labels or Instructions)

Dialogs, Confirmations & Status Messages

  • Dialog close buttons — visible close button (aria-label="Close dialog") on CTS Razor dialogs (Epa.cshtml, EpaEdit.cshtml, Assessments.cshtml) (2.1.1 Keyboard)
  • Delete confirmations$q.dialog() confirmation on delete actions in ManageDomains, ManageEpas, ManageLevels, ManageRoles, ManageBundles, ManageCompetencies, ManageBundleCompetencies, ManageSessionCompetencies (Razor + Vue) (3.3.4 Error Prevention)
  • StatusBanner reuseStatusBanner now exposes v-model:visible so dismissed banners reopen on subsequent saves; AssessmentEpaEdit binds v-model:visible="success" to fix a regression where the "EPA Saved" banner stayed hidden after re-saving. Close button repositioned inline via inline-actions (4.1.3 Status Messages)
  • Themed error banners — inline bg-red-5 text-white error blocks on AssessmentEpa, AssessmentEpaEdit, and AssessmentCompetency now render via <StatusBanner type="error">, inheriting the accessible tinted background and role="alert" from the shared component
  • ManageCompetencies empty state — show a descriptive empty-state message when no competencies match the current filter instead of rendering an empty region

Color & Contrast

  • Brand color alignmentcolor="green" / "red" / "red-5" replaced with brand tokens (positive / negative) on add/create/delete buttons; success banners switched from bg-green text-white to bg-positive text-white; "This Week" badge from greenpositive; Cancel buttons switched from primary to secondary to reduce visual weight (1.4.3 Contrast (Minimum))
  • AssessmentBubble rating redesign — the rating circles in MyAssessments become <button> elements with the numeric rating inside, uniform UC Davis blue palette (blue-70blue-100), bold white text, and a gold focus-visible ring. 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

  • Empty q-table column headers — add descriptive labels ("Actions", "Details") in AssessmentList, ManageBundles, ManageBundleCompetencies, Assessments.cshtml, AuditList (1.3.1 Info and Relationships)
  • AuditList Details 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 on
  • CourseStudents column alignment — center the "Assess Competency" and "Remove" action columns (header + cells) so the icons no longer left-align out of the header

Images

  • Student photo alt text — add descriptive alt text to the photo in AssessmentEpaEdit (1.1.1 Non-text Content)

Bug Fixes (pre-existing)

  • Delete Level button was non-functionalManageLevels.cshtml's "Delete Level" existed since the original commit but had no click handler or delete method. Wired it to DELETE /api/cts/levels/{levelId} with a confirmation dialog; button now only appears when editing an existing level.
  • ManageLevels Cancel button didn't hide the form — the Cancel button was added alongside clearLevel(), but the form's v-if gated on Object.keys(level).length (always truthy since emptyLevel has ten keys). Gate switched to showForm so Cancel actually dismisses the form.
  • ManageMilestones parallel load was sequentialPromise.resolve([...]) should have been Promise.all([...]); the three API calls were started but never awaited correctly and could race with the loaded flag.
  • CompetencyHierarchyDto.Children typing mismatch — declared IEnumerable<CompetencyHierarchyDto> but the controller cast it to List<> to call .Add(). Declaration changed to List<> and the cast removed.
  • ManageCourseCompetencies stored term lookup — the stored term from localStorage was used as-is even when stale; now looks up the matching term by termCode in the current term list and falls back to the latest term if not found.

Code Quality

  • Strict equality fixes (==/!====/!==) in Razor views
  • Minor cleanup in Razor view scripts

Copilot AI review requested due to automatic review settings April 1, 2026 04:46
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 useRouteFocus composable 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.

Comment thread web/Areas/CTS/Views/ManageLevels.cshtml Outdated
Comment thread web/Areas/CTS/Views/Epa.cshtml Outdated
Comment thread web/Areas/CTS/Views/Index.cshtml Outdated
Comment thread VueApp/src/CTS/pages/ManageEpas.vue
@rlorenzo rlorenzo force-pushed the VPR-104-accessibility-audit-CTS branch from 2dc345b to 901cec9 Compare April 1, 2026 16:50
@rlorenzo rlorenzo requested a review from Copilot April 1, 2026 17:17
Copy link
Copy Markdown

Copilot AI left a comment

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 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 uses asp-asp-add-nonce (typo) instead of the established asp-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

  • getStudents uses strict inequality against studentsLoadedForService. Since service can be set from localStorage (string) and from API defaults (number), this.service !== this.studentsLoadedForService can 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
                    }

Comment thread web/Areas/CTS/Views/Epa.cshtml
Comment thread web/Areas/CTS/Views/Epa.cshtml
@rlorenzo
Copy link
Copy Markdown
Contributor Author

rlorenzo commented Apr 1, 2026

@bsedwards Some bugs were fixed in the automated review that you might want to double-check.

  1. ManageLevels.cshtml:94 - milestone: this.type === "milestone" used lowercase "milestone" but the toggle value is "Milestone". This meant creating a Milestone-type level would set milestone: false, so it would never be flagged as a milestone level.
  2. Epa.cshtml:205 - scheduledServices.map(s => s.serviceID) used serviceID (capital ID), but the API returns camelCase serviceId. This produced an array of undefined, so the "currently scheduled" indicator next to services in the dropdown never displayed.
  3. Index.cshtml:8 - asp-asp-add-nonce (doubled asp-) is not a valid TagHelper attribute. The CSP nonce was never applied to this script tag, meaning it could be blocked by Content Security Policy. (The script was empty, so no functional impact, but it would fail if content were added.)

Copy link
Copy Markdown

Copilot AI left a comment

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 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.service can 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 normalizing service/studentsLoadedForService to 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

Comment thread web/Areas/CTS/Views/ManageLevels.cshtml Outdated
Comment thread web/Areas/CTS/Views/ManageEpas.cshtml Outdated
Comment thread web/Areas/CTS/Views/ManageDomains.cshtml Outdated
Comment thread VueApp/src/CTS/pages/ManageRoles.vue Outdated
Comment thread VueApp/src/CTS/pages/ManageDomains.vue Outdated
Comment thread VueApp/src/CTS/pages/ManageLevels.vue Outdated
Comment thread VueApp/src/CTS/pages/ManageEpas.vue Outdated
Comment thread VueApp/src/CTS/pages/ManageBundles.vue Outdated
Comment thread VueApp/src/CTS/pages/ManageBundles.vue Outdated
Copy link
Copy Markdown

Copilot AI left a comment

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 24 out of 24 changed files in this pull request and generated 1 comment.

Comment thread VueApp/src/CTS/router/index.ts
@rlorenzo rlorenzo force-pushed the VPR-104-accessibility-audit-CTS branch 3 times, most recently from 177ffdc to 8555e5b Compare April 2, 2026 16:08
@rlorenzo rlorenzo requested a review from Copilot April 2, 2026 16:08
Copy link
Copy Markdown

Copilot AI left a comment

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 24 out of 24 changed files in this pull request and generated 1 comment.

Comment thread web/Areas/CTS/Views/ManageDomains.cshtml
@rlorenzo rlorenzo force-pushed the VPR-104-accessibility-audit-CTS branch from 8555e5b to cedea92 Compare April 2, 2026 18:29
@rlorenzo rlorenzo requested a review from Copilot April 2, 2026 21:21
Copy link
Copy Markdown

Copilot AI left a comment

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 27 out of 27 changed files in this pull request and generated 1 comment.

Comment thread web/Areas/CTS/Views/ManageLevels.cshtml
Copy link
Copy Markdown

Copilot AI left a comment

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 27 out of 27 changed files in this pull request and generated 1 comment.

Comment thread web/Areas/CTS/Views/ManageLevels.cshtml
@rlorenzo rlorenzo requested a review from Copilot April 3, 2026 00:47
@rlorenzo rlorenzo force-pushed the VPR-104-accessibility-audit-CTS branch from 899e4bf to 1c8f7a6 Compare April 3, 2026 18:59
@rlorenzo rlorenzo force-pushed the VPR-104-accessibility-audit-CTS branch 5 times, most recently from 2b59ac1 to 99f2309 Compare April 10, 2026 16:07
@rlorenzo rlorenzo force-pushed the VPR-104-accessibility-audit-CTS branch from 99f2309 to f586754 Compare April 12, 2026 19:06
@rlorenzo rlorenzo requested a review from bsedwards April 12, 2026 23:41
rlorenzo added 11 commits April 12, 2026 18:39
- 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
@rlorenzo rlorenzo force-pushed the VPR-104-accessibility-audit-CTS branch from a712ebe to 66d60b9 Compare April 13, 2026 02:03
Base automatically changed from VPR-104-accessibility-audit-base to main April 13, 2026 21:31
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.

2 participants