Skip to content

fix(a11y): label 19 form fields, scope 19 tables, honour reduced motion, delete an unreachable settings dialog - #469

Merged
rubenvdlinde merged 1 commit into
developmentfrom
fix/a11y-labels-tables-motion
Aug 9, 2026
Merged

fix(a11y): label 19 form fields, scope 19 tables, honour reduced motion, delete an unreachable settings dialog#469
rubenvdlinde merged 1 commit into
developmentfrom
fix/a11y-labels-tables-motion

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Five gates, 52 findings, all real. hydra-gates 48c88ba1e0d049f8f38538c33e790d3e603c55d0, measured against origin/development (3a542f28).

gate before after
gate-32 semantic-controls 1 PASS
gate-39 button-name 3 PASS
gate-40 form-label-association 19 PASS
gate-43 table-headers 19 PASS
gate-45 prefers-reduced-motion 10 PASS

src/navigation/Configuration.vue is deleted — it could never run

This one file held 13 of gate-40's 19 findings and 1 of gate-43's 19. Before touching it, four independent things say it is unreachable:

  • Zero importers anywhere in src/, tests/, or the webpack config.
  • It POSTs and GETs /index.php/apps/**opencatalogi**/configurationa different app's endpoint.
  • It translates with t('forms', …)a third app's namespace.
  • Its <table> is nested inside a <p>, which is invalid HTML the parser would unwind.

It is an OpenCatalogi leftover duplicating the surface SoftwareCatalogSettings.vue owns, and softwarecatalog's own settings are read and written by SettingsController. Nothing is left unguarded and no capability is lost — the standing lesson from #456 (deleting the dead code would have removed the guards) does not apply here, because this file contains no guard and is not the sole writer of anything.

Fourteen findings go away because the code that produced them is gone, not because a checker was talked out of them.

gate-43 — every data table names its columns

scope="col" on 18 header rows across 12 components. Two empty action headers get a .hidden-visually name (a Nextcloud core class, core/css/global.css — not invented here).

gate-40 — 19 controls a screen reader could not name

  • ViewObject.vue — the four property editors take :aria-label="getPropertyDisplayName(key)", which is exactly the text the Property column already shows. The programmatic name matches the visible one without rendering the label twice. The select-all and per-attachment checkboxes get real names (Select all attachments, Select <filename>).
  • MergeObject.vue — the custom-value field gets a label.

gate-45 — reduced motion, per selector

A @media (prefers-reduced-motion: reduce) block inside each of the 10 style blocks, neutralising only the selectors that actually declare transition/animation. No global reset — nothing else changes appearance for anyone.

gate-39 / gate-32

The three ObjectModal pencil buttons now say what they do ("Change catalogue", "Change register", "Change schema"), not what they look like.

The clickable errors tile in ArchiMateImportExport is now a real <button type="button">, :disabled when the error count is zero — so it is keyboard-reachable exactly when it does something, which a role/tabindex/@keydown triple on a <div> would not have got right. The UA button chrome is neutralised in CSS so it renders identically to its sibling tiles.

Also fixed while here

Four Dutch UI strings in the Dashboard statistics tables — Aantal/BeherenCount/Manage. Ordinary words, no VNG standardised term involved, no test asserts on them; this codebase is English.


Can-fail proof

Reverting all 19 files to origin/development and re-running the suite puts every count back exactly:

[gate-32] semantic-controls:        FAIL — 1
[gate-39] button-name:              FAIL — 3
[gate-40] form-label-association:   FAIL — 19
[gate-43] table-headers:            FAIL — 19
[gate-45] prefers-reduced-motion:   FAIL — 10

Other measurements

  • vitest215/215 across 19 files.
  • stylelint — clean (only the two pre-existing rule-deprecation notices).
  • check:manifest — Ajv PASS (0 errors).
  • Full-repo gate run on this branch: 13 → 8 failing. No other gate count moved.

…on, and delete an unreachable settings dialog

Five gates, 52 findings, all real. hydra-gates 48c88ba, measured against
origin/development (3a542f2).

  gate-32 semantic-controls        1 -> PASS
  gate-39 button-name             3 -> PASS
  gate-40 form-label-association 19 -> PASS
  gate-43 table-headers          19 -> PASS
  gate-45 prefers-reduced-motion 10 -> PASS

gate-43 — every data table now names its columns
  `scope="col"` on 18 header rows across 12 components. The one finding that
  was not a missing `scope=` was `src/navigation/Configuration.vue`'s
  `<table>` with no `<th>` at all — see below.

gate-40 — 19 controls a screen reader could not name
  - `src/modals/object/ViewObject.vue`: the four property editors get
    `:aria-label="getPropertyDisplayName(key)"`, which is exactly the text the
    Property column shows, so the programmatic name matches the visible one
    without rendering it twice. The select-all and per-attachment checkboxes
    get real names.
  - `src/modals/object/MergeObject.vue`: the custom-value field gets a label.
  - The other 13 were all in `src/navigation/Configuration.vue`.

src/navigation/Configuration.vue is DELETED — it could never run
  Zero importers anywhere in src/, tests/ or the webpack config. It POSTs to
  `/index.php/apps/OPENCATALOGI/configuration` — a different app's endpoint —
  and translates with `t('forms', …)`, a third app's namespace. Its `<table>`
  is nested inside a `<p>`, which is invalid HTML. It is an OpenCatalogi
  leftover duplicating the settings surface `SoftwareCatalogSettings.vue`
  owns, and softwarecatalog's own settings are written by SettingsController.
  Nothing is left unguarded and no capability is lost: 13 gate-40 findings and
  1 gate-43 finding disappear because the code that produced them is gone.

gate-45 — reduced motion, per selector
  A `@media (prefers-reduced-motion: reduce)` block in each of the 10 style
  blocks, neutralising only the selectors that declare transition/animation.
  No global reset; nothing else changes appearance.

gate-39 / gate-32
  The three ObjectModal pencil buttons say what they do ("Change catalogue",
  "Change register", "Change schema"). The clickable errors tile in
  ArchiMateImportExport is now a real `<button type="button">`, disabled when
  the error count is zero, so it is keyboard-reachable exactly when it does
  something — plus the UA button chrome is neutralised so it renders
  identically to its sibling tiles.

Also fixed while here: four Dutch UI strings in the Dashboard statistics
tables ("Aantal"/"Beheren" -> "Count"/"Manage"). Ordinary words, not VNG
standardised terms; this codebase is English.

Can-fail proof: reverting all 19 files to origin/development puts every count
back exactly — 1, 3, 19, 19, 10.

vitest 215/215, stylelint clean, check:manifest Ajv PASS (0 errors). No other
gate count moved.
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/softwarecatalog @ f7c5e56

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-09 15:28 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit f5538e7 into development Aug 9, 2026
29 checks passed
@rubenvdlinde
rubenvdlinde deleted the fix/a11y-labels-tables-motion branch August 9, 2026 15:35
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.

1 participant