fix(a11y): label form controls, scope table headers, make KPI tiles real links - #301
Merged
Conversation
…eal links Closes four accessibility gates measured at origin/development with hydra-gates 48c88ba (gate-32: 3, gate-40: 7, gate-43: 10, gate-44: 1). gate-32 semantic-controls — the two KPI tiles hung @click on a plain <div> and MyMandatoryTrainingWidget's "Start" was an <a> with no href, so none of the three was in the tab order. All three now render a real link: the tiles pass CnStatsBlock its `route` prop (which renders a <router-link>) instead of `clickable` + a manual $router.push, and "Start" is a <router-link>. Middle-click and open-in-new-tab now work, which they never did. gate-40 form-label-association — 7 controls had only a placeholder, which is not a label. The two textareas that already sit under a visible <h3> are now associated with it via aria-labelledby rather than growing a second, duplicate visible label; the three controls with no visible text (an option row inside a v-for, a per-row cohort input in a table) get an aria-label naming their row. gate-43 table-headers — 41 <th> across 10 tables were all column headers in <thead>; each gets scope="col". No table has row headers. gate-44 autocomplete-attr — recipient-email is declared autocomplete="off", NOT "email": it collects a third party's address (an employer, a receiving school), never the signed-in user's own, so offering the user's own address is the wrong suggestion. Comment records the reasoning. No waivers, no excludes, no threshold changes. Each of the four gates was re-run with one fix reverted and confirmed to return to FAIL naming that exact item, so none of these passes is a gate that simply stopped looking.
Contributor
Quality Report — ConductionNL/scholiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 750/750 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-09 14:50 UTC
Download the full PDF report from the workflow artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes four accessibility gates, measured at
origin/developmentwith a fresh clone ofConductionNL/.github.[hydra-gates] gate package: 48c88ba1e0d049f8f38538c33e790d3e603c55d0Before → after
<th>)No other gate changed verdict.
gate-16 spec-coveragebriefly went red on the two methods this PR adds and is green again now that both carry a@specanchor that resolves (gate-46confirms).What was actually wrong
gate-32 — both KPI tiles hung
@clickon a bare<div>, andMyMandatoryTrainingWidget's "Start" was an<a>with nohref, so none of the three was focusable. Rather than boltrole/tabindex/@keydownonto a div, the tiles now passCnStatsBlockitsrouteprop, which renders a real<router-link>; "Start" became a<router-link>too. Verified against the declared@conduction/nextcloud-vue@2.2.0-vue3.3, which declaresemits: ['click']and rendersrouteasrouter-link— note the checked-innode_moduleshad1.0.0-beta.190(the Vue 2 line), so reading that copy would have described a different component.gate-40 — a
placeholderis not a label. The two textareas already sitting under a visible<h3>are associated with it viaaria-labelledbyrather than gaining a second, duplicated visible label; controls with genuinely no visible text get anaria-labelnaming their row.gate-43 — all 41
<th>are column headers inside<thead>; each getsscope="col". No table has row headers, so noscope="row"was applied blindly.gate-44 —
recipient-emailisautocomplete="off", not"email". It collects a third party's address (an employer, a receiving school), never the signed-in user's own; suggesting the user's own address there would be wrong. The reasoning is recorded in a comment next to the attribute.Evidence each gate can still fail
No waivers, no
exclude, no.skip, no thresholds. After all four went green, one fix per gate was reverted and the suite re-run — every gate returned to FAIL naming exactly the reverted item:Then restored, and all four returned to PASS.
npx eslinton all 17 touched files: 0 errors (89 pre-existing style/v-htmlwarnings, unchanged by this PR;npm run lintsets no--max-warnings).