58dd2773a2
* feat(web): show download progress for voice mode Whisper Web model First-time use of voice mode with the Whisper Web engine downloads ~40–240 MB of model files before the first transcription. The existing modelLoad progress was only surfaced via the mic-button tooltip, so the download felt silent — users couldn't tell whether it was progressing, stuck, or how much was left. Adds a compact inline indicator next to the mic button that renders a percent label and a Progress bar while the model is downloading, and a short error label when load fails. The indicator stays hidden for the Web Speech and server-Whisper engines (which don't download models) and clears once the model is ready. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(web): tidy VoiceModelLoadIndicator class names and a11y - drop unused cn() import (single-string className doesn't need it) - drop redundant max-w-[8rem] (w-32 already equals 8rem in tailwind) - include modelLabel in the error state's aria-label so screen readers identify which model failed Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(web): a11y improvements for VoiceModelLoadIndicator - Replace two separate loading/error divs with a single stable role="status" wrapper so AT announces the loading→error transition (newly-inserted live regions are silently ignored by most screen readers). - Move numeric progress to role="progressbar" + aria-valuenow/min/max on the inner div; removes per-tick live-region announcements. - Drop redundant aria-live="polite" (role="status" implies it per ARIA spec). - Align error visible text with aria-label: both now use modelLabel ("Whisper Base failed to load") instead of the generic string. - Standardize tooltip wording to match indicator: "Downloading {model}…" instead of "Loading model…". - Update test: error branch expects model-specific label. * refactor(web): fix nested progressbar and clamp tooltip percent - Remove role="progressbar" + aria-value* from the wrapper div; <Progress> (Radix) already renders its own progressbar — the nested node confused AT. Pass aria-label directly to <Progress> for the accessible name. - Drop outer aria-label on the loading state (Radix progressbar carries it). - Clamp tooltip percent in voice-input-button.tsx to 0–100 and handle NaN (mirrors clampPercent in the indicator, keeps UI consistent on edge values from transformers.js). - Update test: assert aria-label via getByRole("progressbar").getAttribute(). * fix(web): guard tooltip percent against NaN; assert error aria-label in test - Tooltip loading percent: add Number.isFinite guard so NaN from transformers.js progress produces "0%" instead of "NaN%". - Test: assert aria-label on error state root matches modelLabel. * fix(web): add w-32 to error variant to prevent layout shift on state change Loading branch uses w-32 to hold a stable 128px slot in the toolbar. Error branch lacked it, so the mic button shifted on loading→error transition. --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>