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
+8
View File
@@ -964,6 +964,11 @@ import createResearchSynapse from './researchSynapse.js';
return;
}
// Mark the chat log busy while streaming so screen readers wait for the
// settled response instead of announcing every token. Cleared in finally.
const _chatLog = document.getElementById('chat-history');
if (_chatLog) _chatLog.setAttribute('aria-busy', 'true');
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
@@ -2702,6 +2707,9 @@ import createResearchSynapse from './researchSynapse.js';
}
} finally {
clearProcessingProbe();
// Streaming done — let screen readers announce the settled response.
const _chatLogDone = document.getElementById('chat-history');
if (_chatLogDone) _chatLogDone.setAttribute('aria-busy', 'false');
// Always clean up research tracking regardless of background state
_researchingStreamIds.delete(streamSessionId);
if (_researchingStreamIds.size === 0) {