feat(web): VOICE-3 composer mic toggle + live partials #780

Merged
code-lead merged 2 commits from dev/775 into main 2026-05-02 22:53:14 +00:00
Collaborator

Mic button + recording state machine in the Composer (VOICE-3 / #775).

  • useTranscribeHealth fetches the VOICE-2 health probe; mic button is hidden when enabled: false or the probe fails
  • useMicDictation drives idle → requesting-permission → recording → uploading → idle with hard cap at speech.max_audio_seconds
  • Pulsing dot + mm:ss elapsed timer while recording; Esc cancels at higher priority than palette/abort handlers
  • SSE partials via fetch + ReadableStream (matching useArchitectStream pattern); debounced ~1 s for screen-reader announcements
  • Final transcript inserted at caret position; appends with leading space when textarea lost focus
  • Vitest: 6 new tests covering health-gate, state-machine transitions, Esc cancel, final insert, and error toast

Test plan

  • bun run test — all 19 composer tests pass (6 new voice tests)
  • bun x @biomejs/biome@^2 check — clean on changed files

Closes #775

Mic button + recording state machine in the Composer (VOICE-3 / #775). - `useTranscribeHealth` fetches the VOICE-2 health probe; mic button is hidden when `enabled: false` or the probe fails - `useMicDictation` drives `idle → requesting-permission → recording → uploading → idle` with hard cap at `speech.max_audio_seconds` - Pulsing dot + `mm:ss` elapsed timer while recording; `Esc` cancels at higher priority than palette/abort handlers - SSE partials via `fetch` + `ReadableStream` (matching `useArchitectStream` pattern); debounced ~1 s for screen-reader announcements - Final transcript inserted at caret position; appends with leading space when textarea lost focus - Vitest: 6 new tests covering health-gate, state-machine transitions, Esc cancel, final insert, and error toast ## Test plan - `bun run test` — all 19 composer tests pass (6 new voice tests) - `bun x @biomejs/biome@^2 check` — clean on changed files Closes #775
dev self-assigned this 2026-05-02 22:02:47 +00:00
feat(web): VOICE-3 composer mic toggle + live partials (#775)
Some checks failed
qa / dockerfile (pull_request) Successful in 10s
qa / qa (pull_request) Failing after 1m15s
a7cbf8438a
Adds voice dictation to the Composer: mic button (hidden when health probe
reports disabled, disabled when browser lacks getUserMedia), idle →
requesting-permission → recording → uploading → idle state machine,
pulsing recording dot + mm:ss elapsed timer, SSE partial preview band
(debounced 1 s for screen readers), final transcript inserted at caret
position, Esc cancels recording at higher priority than existing handlers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(web): type composer fetchSpy as MockInstance<typeof fetch>
All checks were successful
qa / dockerfile (pull_request) Successful in 5s
qa / qa (pull_request) Successful in 2m44s
7f02b04e30
CI on PR #780 went red because `ReturnType<typeof vi.spyOn>` widens to a
generic `MockInstance<(this: unknown, ...args: unknown[]) => unknown>`
which TS rejects when `mockImplementation` receives a typed
`(input: RequestInfo | URL) => Promise<Response>` callback. Importing
`MockInstance` and parameterising it on `typeof fetch` keeps the four
existing test bodies unchanged while satisfying tsc.
dev requested review from reviewer 2026-05-02 22:47:40 +00:00
reviewer approved these changes 2026-05-02 22:52:18 +00:00
reviewer left a comment

All ACs met; CI green.

  • State machine, cleanup, abort, and caret-insert logic are all correct
  • Debounced partials, prefers-reduced-motion gate, aria-pressed/aria-label swap, and mic-hidden-when-disabled all verified
  • Tests cover hidden/visible, recording transitions, Esc cancel, caret insert, and error toast path

Nit (non-blocking): timer interval could call recorder.stop() a second time in the 1 s window between auto-stop firing and onstop setting recorderRef.current = null — browsers throw InvalidStateError there, though it is console-only and recovers cleanly.

All ACs met; CI green. - State machine, cleanup, abort, and caret-insert logic are all correct - Debounced partials, `prefers-reduced-motion` gate, `aria-pressed`/`aria-label` swap, and mic-hidden-when-disabled all verified - Tests cover hidden/visible, recording transitions, Esc cancel, caret insert, and error toast path Nit (non-blocking): timer interval could call `recorder.stop()` a second time in the 1 s window between auto-stop firing and `onstop` setting `recorderRef.current = null` — browsers throw `InvalidStateError` there, though it is console-only and recovers cleanly.
code-lead deleted branch dev/775 2026-05-02 22:53:14 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!780
No description provided.