Prerequisites
Install Method
Docker (docker compose up)
Operating System
Linux
Steps to Reproduce
Two small, related accessibility problems in the chat UI (static/js/chat.js). Both are client-side and reproduce with any model/backend.
A. The live "Thinking…" timer ignores prefers-reduced-motion:
docker compose up -d --build and open the chat in a browser.
- Turn on reduced motion. Either at the OS level (for example macOS: System Settings > Accessibility > Display > Reduce motion) or in DevTools: open the Command Menu, run "Show Rendering", and set "Emulate CSS media feature prefers-reduced-motion" to
reduce.
- Send a prompt to a model that streams a thinking/reasoning block, and watch the "Thinking…" header while it streams.
B. The send/stop button has no accessible name:
- Open the chat with a screen reader running (VoiceOver on macOS/iOS, or NVDA on Windows), or open DevTools > Elements > Accessibility pane.
- Focus the round send button next to the composer (the icon-only button that becomes a stop button while a response streams).
- Inspect its computed accessible name, both when idle and while a response is streaming.
Expected Behaviour
- A. With reduced motion enabled, the thinking timer should not animate. It should update at a low, whole-second cadence (or not visibly tick) rather than repainting several times per second, honouring the user's motion preference like the rest of the UI.
- B. The send/stop button should expose an accessible name that reflects its current action (for example "Send message" when idle and "Stop generation" while a response streams), so a screen-reader user can tell the two states apart.
Actual Behaviour
- A. The timer animates regardless of the motion preference. It is driven by a self-perpetuating
requestAnimationFrame loop that rewrites the header text roughly 60 times a second (a tenths-of-a-second readout) for the entire thinking duration, so a user who asked for reduced motion still gets a rapidly repainting counter.
- B. The button has no
aria-label. Its state is only carried in the title attribute, and title alone is not reliably announced by iOS VoiceOver on a <button>. As a result the accessible name is effectively empty (or an unlabelled "button"), and a screen-reader user cannot tell whether the control will send or stop, or notice that it switched to "Stop generation" mid-stream.
Logs / Screenshots
No screenshots — both problems are non-visual (one is behavioural under reduced motion, the other is screen-reader-only). The fixing PR includes a node-driven test that reproduces both contracts, and the PR description lists manual steps to verify with reduced motion emulated and with the DevTools accessibility tree.
Model / Backend (if relevant)
Not relevant — client-side.
Are you willing to submit a fix?
Yes — I can open a PR.
Additional Information
Both live in static/js/chat.js. For (A) the timer is set up where the live thinking header is created (_tickThinkTimer / _thinkTimerRAF); the fix drives it from a matchMedia-gated setInterval instead of requestAnimationFrame. For (B) the button already keeps its title in sync per mode, so the fix mirrors that live title into aria-label (which screen readers do announce) from the same single source of truth. PR to follow, targeting dev.
Prerequisites
devbranch (the default branch you get on clone, where fixes land first) and the bug still reproduces there. Pleasegit pullthe latestdevbefore filing.Install Method
Docker (docker compose up)
Operating System
Linux
Steps to Reproduce
Two small, related accessibility problems in the chat UI (
static/js/chat.js). Both are client-side and reproduce with any model/backend.A. The live "Thinking…" timer ignores
prefers-reduced-motion:docker compose up -d --buildand open the chat in a browser.reduce.B. The send/stop button has no accessible name:
Expected Behaviour
Actual Behaviour
requestAnimationFrameloop that rewrites the header text roughly 60 times a second (a tenths-of-a-second readout) for the entire thinking duration, so a user who asked for reduced motion still gets a rapidly repainting counter.aria-label. Its state is only carried in thetitleattribute, andtitlealone is not reliably announced by iOS VoiceOver on a<button>. As a result the accessible name is effectively empty (or an unlabelled "button"), and a screen-reader user cannot tell whether the control will send or stop, or notice that it switched to "Stop generation" mid-stream.Logs / Screenshots
No screenshots — both problems are non-visual (one is behavioural under reduced motion, the other is screen-reader-only). The fixing PR includes a node-driven test that reproduces both contracts, and the PR description lists manual steps to verify with reduced motion emulated and with the DevTools accessibility tree.
Model / Backend (if relevant)
Not relevant — client-side.
Are you willing to submit a fix?
Yes — I can open a PR.
Additional Information
Both live in
static/js/chat.js. For (A) the timer is set up where the live thinking header is created (_tickThinkTimer/_thinkTimerRAF); the fix drives it from amatchMedia-gatedsetIntervalinstead ofrequestAnimationFrame. For (B) the button already keeps itstitlein sync per mode, so the fix mirrors that livetitleintoaria-label(which screen readers do announce) from the same single source of truth. PR to follow, targetingdev.