Skip to content

refactor(voice): load STT availability through React Query - #6224

Merged
waleedlatif1 merged 3 commits into
stagingfrom
refactor/voice-settings-query
Aug 3, 2026
Merged

refactor(voice): load STT availability through React Query#6224
waleedlatif1 merged 3 commits into
stagingfrom
refactor/voice-settings-query

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

useSpeechToText fetched /api/settings/voice inside a useEffect and stored the result in useState behind a hand-rolled mountedRef guard.

Concretely that meant:

  • No cache or dedupe — every mount issued a fresh request; two simultaneously mounted consumers issued two.
  • No cancellation — no AbortSignal was passed, so the response was fetched and parsed even after unmount; the guard only suppressed the setState.
  • Bypassed hooks/queries/**, where every other server read in the app lives. It also escaped bun run check:react-query, whose audit only covers useQuery/useMutation call sites — so nothing flagged it.

Adds hooks/queries/voice.ts with a key factory and an infinite staleTime (the value is server env read at request time, so it cannot change within a session), and a caller-controlled enabled so clients lacking AudioContext/WebSocket/getUserMedia never issue the request at all.

Hydration

Unchanged, and deliberately so. SSR renders unavailable; the first client render also resolves unavailable because data is undefined until the fetch settles, so the markup matches. No initialData — adding it would break that guarantee. A failed request also still reads as unavailable rather than throwing, matching the previous .catch.

mountedRef stays — it is still load-bearing for the streaming lifecycle (WebSocket, MediaStream, AudioContext teardown).

Blast radius

The only consumer is the workspace home input (app/workspace/[workspaceId]/home/components/user-input/user-input.tsx), which gates the mic button on isSupported. Nothing on the chat surface uses this hook any more.

Type of Change

  • Refactor

Testing

New hooks/queries/voice.test.tsx covers availability, the disabled gate, failure reading as unavailable, and dedupe across simultaneous consumers. The enabled-gate test was verified to fail when the option is removed. 1058 tests pass across hooks; typecheck, lint and check:react-query all clean.

Not exercised in a live browser — worth confirming the mic button still appears on the workspace home input.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 3, 2026 8:12pm

Request Review

@cursor

cursor Bot commented Aug 3, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Refactor of capability probing only; mic gating behavior is intentionally preserved and covered by new tests.

Overview
Moves server STT availability from a one-off useEffect + useState in useSpeechToText into hooks/queries/voice.ts (useVoiceSettings), aligning with the app’s React Query pattern and check:react-query.

useVoiceSettings calls /api/settings/voice with infinite staleTime, optional enabled (so browsers without capture APIs skip the request), AbortSignal cancellation, and retryOnMount: true so a transient failure does not permanently hide the mic under app QueryClient defaults.

useSpeechToText now derives isSupported from browser capability checks plus sttAvailable === true; hydration behavior stays the same (no initialData, unavailable until fetch succeeds). Streaming teardown still uses mountedRef.

Adds voice.test.tsx for the query hook and chats.test.tsx teardown via mountedRoots / afterEach unmount to avoid observer leaks.

Reviewed by Cursor Bugbot for commit f06b866. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR moves voice-provider availability loading into a cached, cancellable React Query hook and updates speech-to-text capability gating.

  • Adds a voice-settings query key, fetcher, browser-capability gate, and retry-on-remount behavior.
  • Adds coverage for availability, disabled requests, failures, recovery, and deduplication.
  • Registers and unmounts test roots after each voice and chat query test.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported test-root leak is fixed because every created root is registered and synchronously unmounted after each test.

Important Files Changed

Filename Overview
apps/sim/hooks/queries/voice.ts Introduces the React Query hook for STT availability with cancellation, infinite freshness, conditional enablement, and retry-on-mount.
apps/sim/hooks/use-speech-to-text.ts Replaces local effect-driven availability state with the shared query while preserving browser capability and hydration gating.
apps/sim/hooks/queries/voice.test.tsx Covers the query contract and now registers every created root for complete afterEach teardown.
apps/sim/hooks/queries/chats.test.tsx Applies the same root-registration and teardown fix to the existing chat query tests.

Reviews (3): Last reviewed commit: "fix(voice): let a failed STT probe recov..." | Re-trigger Greptile

Comment thread apps/sim/hooks/queries/voice.test.tsx
@waleedlatif1
waleedlatif1 force-pushed the refactor/voice-settings-query branch from 384ab77 to b01eab8 Compare August 3, 2026 19:58
useSpeechToText fetched `/api/settings/voice` inside an effect and stored
the result in useState behind a hand-rolled mountedRef guard: no cache, no
dedupe across mounts, and no AbortSignal, so the response was fetched and
parsed even after unmount. Two simultaneously mounted consumers issued two
requests. It also bypassed hooks/queries/**, which is where every other
server read in the app lives — and it escaped `check:react-query`, whose
audit only covers useQuery/useMutation call sites.

The value is server env read at request time, so it cannot change within a
session; the new hook uses an infinite staleTime and a caller-controlled
`enabled` so clients without the audio APIs never issue the request.

Hydration is unchanged: SSR renders unavailable, and the first client
render still resolves unavailable because `data` is undefined until the
fetch settles. No initialData, deliberately — adding it would break that.

mountedRef stays; it is still load-bearing for the streaming lifecycle.
renderHookWithClient created a React root per test but never tore it
down, so trees stayed mounted with live QueryClient observers until
worker teardown and async notifications could cross test boundaries.

Audited every test in the repo using createRoot: 51 of 53 already
unmount. The two that did not were both mine — voice.test.tsx here and
chats.test.tsx from #6223 — so both are fixed and the pattern is now
uniform.
@waleedlatif1
waleedlatif1 force-pushed the refactor/voice-settings-query branch from b01eab8 to 7af7b83 Compare August 3, 2026 20:00
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/hooks/queries/voice.ts
The app QueryClient sets retryOnMount: false and retry: 1, and
refetchOnWindowFocus only refetches stale queries — which an infinite
staleTime never becomes. So one transient failure cached the error for
the life of the client and hid the mic until a full page reload.

The effect this replaced refetched on every run, so retryOnMount: true
restores parity: no refetch after success, a retry per mount after
failure. Test asserts recovery under the app's real query defaults and
fails without the override.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit f06b866. Configure here.

@waleedlatif1
waleedlatif1 merged commit c759a88 into staging Aug 3, 2026
30 checks passed
@waleedlatif1
waleedlatif1 deleted the refactor/voice-settings-query branch August 3, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant