Skip to content

feat(voice): voice mode can be switched off while dictation stays - #302

Merged
thecodacus merged 3 commits into
mainfrom
feat/voice-mode-off-dictation-on
Oct 23, 2026
Merged

thecodacus merged 3 commits into
mainfrom
feat/voice-mode-off-dictation-on

Conversation

@thecodacus

@thecodacus thecodacus commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

What was asked

Issue #287 asks for a way to turn voice mode off and keep dictation only, for people who just want speech typed into the message box without a spoken conversation.

The change

Settings → Defaults → Voice mode has a new Voice conversations switch. It is on by default, so current setups do not change. The switch lives in core, so it works the same whatever voice mode listens and speaks through (the Voice service or Voice servers).

When it is off:

  • The chat does not show the voice-mode (waveform) button, and Alt+V does nothing. The dictation button stays and works as before.
  • In the settings section, Voice model, Reply without thinking first on and Speaking instructions are hidden, the same way they are hidden for a source that can only recognise speech. Speech detection stays, because dictation uses it. The switch itself is hidden while the source can only recognise speech, since voice mode is not offered then anyway.
  • The server answers 409 when voice mode tries to take a session lease or asks for a spoken reply. Giving a lease back still works, so the source is told straight away that voice mode is no longer in use. Transcription is unchanged, so dictation keeps working.
  • voiceSpeaks() returns false, so the rule for spoken replies is no longer added to every conversation. It is only there for conversations that have had a spoken message, as it already was with a source that cannot speak.

A chat already in voice mode when the switch goes off: in the tab where the setting was changed, voice mode ends at once and gives its lease back (the page already re-reads the settings on voice-config-changed). In any other tab, voice mode ends with the server's message at its next lease renewal (every 25 seconds), and replies before then are not spoken.

A tab reloaded while voice mode was on there, with the switch off by then: voice mode is not brought back, and it does not come back later either. Before, the tab kept meaning to bring it back, so once the switch was on again, closing Settings in that tab (which re-reads the voice settings) started voice mode with the microphone and no click. This also happened on main after Stop using and choosing a source again. Now it is forgotten, as after any other end.

GET /api/voice now includes conversation, and PUT /api/voice accepts it as true or false. If a save leaves it out, it counts as true, the same way the section's other settings fall back to their defaults. The upgrade's voice hand-off from schema 8 now keeps this key along with the other voice-mode settings. Docs updated: docs/guide/voice.md (the switch in Voice mode's settings, and Dictation points to it), docs/guide/settings.md (the Voice mode section's list) and docs/reference/api.md. German strings added.

What it does not do

  • It does not refuse voice-mode transcription (/voice/transcribe without ?purpose=dictation) or prompts sent with voice: true. Voice mode cannot start without a lease, and a tab that was already on ends at its next renewal.
  • Chats that are already open keep the system prompt they were built with. The rule for spoken replies drops out of a conversation that has had no spoken message when that conversation is next opened, as it already does when the source changes.
  • Other tabs keep showing the voice-mode button until they reload. Pressing it then shows the server's refusal.

Tests

  • core/server/test/voice-providers.test.mjs: new test. On by default when nothing is saved. Switching off saves it and GET shows it. voiceSpeaks() is false and an AudioRule built on it leaves a conversation with no spoken message. Lease and speech are refused with 409, a lease is still given back and the source hears it. Dictation transcription works. A non-boolean is refused with 400. Switching back on restores everything.
  • tests/voice.test.mts: validateConfig defaults conversation to true and refuses non-booleans.
  • core/server/test/schema-fingerprint.test.mjs: expectations updated for the saved key.
  • tests/browser/voice.spec.mts: new spec. Voice mode that is on ends and gives back its lease when the switch goes off. The voice-mode button is gone and the dictation button stays. Alt+V, from the page or the message box, starts nothing and takes no lease. Dictation still listens. Switching back on brings the button back. A second new spec: a tab loaded with voice mode to bring back while the switch is off clears it, and switching on afterwards (as closing Settings tells the page) takes no lease and starts nothing.
  • tests/browser/voice-mode.spec.mts: new spec for the switch (saved as clicked, hides the speaking-only sections, kept across a reload). The existing save bodies now include conversation, and the recognition-only case checks that the switch is hidden.
  • Mutation checks: each of the following, done one at a time, made a test fail. Dropping the conversation check from voiceSpeaks, the lease refusal, or the speech refusal. Refusing lease releases too. Skipping the type check. Leaving conversation out of GET. Defaulting a save to off. Treating an absent value as off. Ignoring it in VoiceControl. Not forgetting voice mode to bring back after a reload when it cannot be used (the reload spec above fails: voice mode starts). Not hiding the speaking-only sections. Not saving it. Showing the switch for a recognition-only source.
  • Runs (Mac):
    • Passed: core/server and core/web builds, npm run check:boundaries, core/web typecheck, npm run docs:build (no dead links), voice-providers + schema-fingerprint (50/50), root voice/dictation/hands-free/i18n/settings-search/voice-first tests (142/142), voice and voice-servers extension tests (45/45), browser specs voice, voice-mode and voice-reload (39/39, rerun after the reload fix), voice-source, voice-interaction, voice-windows, settings, shortcuts, language, load-failures and the voice extensions' specs.
    • Failed, same on main: extension-hooks.spec.mts:25 is flaky when the whole file runs (it fails the same way on main), and the 10 server tests in sandbox/folder/link files that fail on this Mac also fail on main.

Closes #287

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a setting to turn spoken voice conversations on or off. Conversations are on by default.
    • When conversations are off, dictation remains available while spoken replies and voice sessions are disabled.
    • Updated voice settings and documentation to explain the new option.

Settings → Defaults → Voice mode has a Voice conversations switch, on by
default so current setups do not change. Off, the chat no longer offers
voice mode: its button and the Alt+V shortcut are gone, and dictation
stays as it is. The switch is core's, so it holds whatever voice mode
listens and speaks through: the Voice service or Voice servers.

While it is off the portal refuses to take a voice session's lease and
to speak a reply (409), and still takes a lease back. Voice mode that is
on ends at once in the tab where it was switched off, and in another tab
when its next lease renewal is refused. Transcription is untouched, so
dictation keeps working. voiceSpeaks() is false, so the rule for spoken
replies is no longer put in every conversation, only in one that has had
a spoken message. The upgrade's voice hand-off from schema 8 keeps the new key with the
other voice mode settings.

Closes #287

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 355284bd-8615-407e-b961-715f1bfe2505
📥 Commits

Reviewing files that changed from the base of the PR and between 2286642 and 0cae0dd.

📒 Files selected for processing: 14
  • core/server/src/api/voice.ts
  • core/server/src/db.ts
  • core/server/test/schema-fingerprint.test.mjs
  • core/server/test/voice-providers.test.mjs
  • core/web/src/api.ts
  • core/web/src/components/VoiceControl.tsx
  • core/web/src/components/VoiceModeSettings.tsx
  • core/web/src/locales/de.ts
  • docs/guide/settings.md
  • docs/guide/voice.md
  • docs/reference/api.md
  • tests/browser/voice-mode.spec.mts
  • tests/browser/voice.spec.mts
  • tests/voice.test.mts
+-----------------------------------------------+
| Finding more bugs than your brain at 4:00 pm. |
+-----------------------------------------------+
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

thecodacus and others added 2 commits October 23, 2026 23:13
…it can be used again

A tab reloaded while voice mode was on there brings it back. When voice
mode could not be used at that load (voice conversations switched off in
another tab, or no source), it was ended, but the tab still meant to bring
it back: the next time it could be used, as when Settings is closed after
voice conversations are switched on again, voice mode started with the
microphone and no click. Ended that way, it is now forgotten, as after any
other end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion points to it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thecodacus
thecodacus merged commit bb0bf66 into main Oct 23, 2026
4 of 5 checks passed
@thecodacus thecodacus mentioned this pull request Oct 23, 2026
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.

Voice extension: option to disable voice mode, keep dictation only

1 participant