v1.47.0
[1.47.0] — 2026-05-18
WS2 #7 + #30 + #31 + #16 — unbound-label accessibility sweep.
🐛 Fixes
fix(a11y): bind every swept form control to an accessible name— four UX-audit findings where inputs had no programmatic label (WCAG 1.3.1 / 3.3.2 / 4.1.2):- #7
scan.js— thedry-runcheckbox andcompany-selectdropdown had labels with nofor; addedhtmlFor(matching the existingids). - #30
deep.js—company/roleinputs had unbound labels; addedid+htmlFor(deep-company,deep-role). - #31
apply.js—url/jdhad unbound labels; addedid+htmlFor(apply-url,apply-jd). - #16
cv.js— the primary markdown<textarea>had no accessible name; bound it viaaria-labelledbyto the visible "Markdown"<h3>(id="cv-md-heading") — SR name == on-screen heading, no new i18n key.
- #7
- Uses the explicit
label[for]↔control[id]pattern already standard inbatch.js/mode-page.js. No new i18n keys; zero behaviour change.
🧪 Tests
test: tests/unbound-label-sweep.test.mjs— 5 cases incl. a binding-integrity check that every newhtmlFor/aria-labelledbyhas a matchingidin-file. 667 → 672. Playwright-verified: on #/scan, #/deep, #/apply everylabel[for]resolves to a control; #/cvaria-labelledbyresolves to the heading.
Verification
$ npm run test:ci
# 672 / 672 · ✓ no .also( leftovers · ✓ CHANGELOG parity: all 8 locales at v1.47.0
# Playwright: scan/deep/apply 2 bound label[for] each (all resolve) · cv aria-labelledby resolves · 0 errors