Accessibility: add labels and toggle states

* Accessibility: ARIA labels and toggle states

Screen readers couldn't name several icon-only controls or tell whether the
tool toggles were on. This adds accessible names and exposes toggle state,
with no behavior or layout change.

- Icon-only buttons get aria-label: web/shell tool toggles, the "more tools"
  overflow button (+ aria-haspopup), and the color-reset buttons.
- Unlabeled inputs/selects get aria-label: memory + skills search, model-picker
  search, memory sort, theme font/density selects, and the new-memory / skill
  (title, when-to-use, how, tags) fields, which only had a visual floating label.
- Toggle state via aria-pressed, kept in sync at the existing .active write
  sites: web/shell toggles (setupToggle) and the Agent/Chat mode buttons
  (initModeToggle). Static aria-pressed added in the markup so the attribute
  exists before JS runs.

Scope: first slice of the ROADMAP accessibility pass. Focus-visible/contrast,
reduced-motion, and modal dialog roles/focus-trap are left for follow-ups.

Checks: node --check static/app.js. No Python touched.

* Accessibility: mark chat log busy while streaming

The chat log is an aria-live="polite" region, so streaming a response
token-by-token made screen readers announce every partial update — noisy and
unreadable. Set aria-busy="true" on #chat-history while a response streams and
back to "false" in the stream's finally block. Assistive tech then waits for
the settled message and announces it once.

Checks: node --check static/js/chat.js.
This commit is contained in:
Kenny Van de Maele
2026-06-02 13:55:05 +02:00
committed by GitHub
parent aa0a9e8b5a
commit cfb7ec1c71
3 changed files with 47 additions and 35 deletions
+4
View File
@@ -1564,6 +1564,8 @@ function initializeEventListeners() {
saveToggleState(st);
agentBtn.classList.toggle('active', mode === 'agent');
chatBtn.classList.toggle('active', mode === 'chat');
agentBtn.setAttribute('aria-pressed', String(mode === 'agent'));
chatBtn.setAttribute('aria-pressed', String(mode === 'chat'));
// Slide the pill to the active button
const toggle = agentBtn.closest('.mode-toggle');
if (toggle) toggle.classList.toggle('mode-chat', mode === 'chat');
@@ -1621,11 +1623,13 @@ function initializeEventListeners() {
const chk = el(checkboxId);
if (chk) chk.checked = saved;
btn.classList.toggle('active', saved);
btn.setAttribute('aria-pressed', String(saved));
btn.addEventListener('click', () => {
const curMode = (loadToggleState().mode) || 'chat';
const chk = el(checkboxId);
chk.checked = !chk.checked;
btn.classList.toggle('active', chk.checked);
btn.setAttribute('aria-pressed', String(chk.checked));
saveToolPref(stateKey, curMode, chk.checked);
showToolToggleToast(stateKey, chk.checked);
if (chk.checked) _showToolSplash(stateKey);