ADR 0291: Remove the speech settings UI
- Status: Accepted
- Date: 2026-09-19
- Deciders: PI-Desktop core
- Amends: ADR 0281 (its v1 product entry)
- Related: 04-ux/06-settings-ia.md · 04-ux/08-component-spec.md · 03-runtime/20-speech.md · 06-delivery/04-e2e-test-plan.md
Context
ADR 0281 added the host speech capability: transcribe / synthesize over an optional AppSettings.speech, two built-in protocols, a high-risk speech.adapter.register plugin adapter, and no audio bytes in the renderer. Its item 5 named the v1 product entry as a Voice card in Settings → AI plus Composer transcription and draft speech.
Two renderer surfaces were built for it. The Composer toolbar exposed mic (transcribe) and read-aloud (speak) buttons over a useComposerSpeech hook, and Settings → AI carried the Voice card. The Composer controls were removed on 2026-09-18 (344ef4ec2, PR #555) while the host services and the plugin-facing speech API stayed. The card then became the only renderer surface: the only consumer of speech/getStatus, speech/transcribe, and speech/synthesize, and the only reader of the settings.speech* catalog. The copy written for the removed controls — chat.transcribe, chat.transcribing, chat.transcribeFailed, chat.speak, chat.speaking, chat.speakFailed, chat.speakSaved — stayed in all eight catalogs with no caller, and the claims in 04-ux/08-component-spec.md §2.5 and ADR 0281 item 5 outlived the controls they described.
The product does not want a speech configuration surface in Settings. It puts a second provider picker per role, plus Whisper / TTS vocabulary, inside the app shell for a capability whose only customers today are plugins and direct IPC callers.
Decision
- Remove the Voice card. Settings → AI renders no speech surface.
apps/desktop/src/features/settings/voice-settings.tsxand itsVoiceSettingsCardmount infeatures/settings/SettingsPage.tsxare deleted. - Remove the renderer leftovers. The AI destination in
lib/settings-search.tsno longer indexessettings.speechTitle,settings.speechTranscribe, orsettings.speechSynthesize; the.settings-row:has(.settings-speech-fields),.settings-speech-fields, and.settings-speech-leadrules leavestyles/settings.css; the thirteensettings.speech*keys leave all eight shipped locale catalogs; and the seven unreferencedchat.transcribe*/chat.speak*keys written for the withdrawn Composer controls leave them too. - Keep the capability. ADR 0281 items 1–4 and 6 stay in force:
speech/getStatus,speech/transcribe,speech/synthesize,validateSpeechSettings, the built-inopenai_audio/openai_chat_audioprotocols, the pluginspeech.adapter.registerpermission, and the renderer API bridge inlib/api.tsare unchanged. Audio bytes still never enter the renderer. - Bindings stay written by a caller.
AppSettings.speechkeeps its shape, validation, and persisted semantics; a binding is written through the host settings API (settings/set), and its consumers are plugins and IPC calls. No migration runs and no stored binding is dropped. - Keep the Composer entry points withdrawn and correct their claims. ADR 0281 item 5's Composer transcription and draft-speech actions stay withdrawn: the controls they describe were already removed in
344ef4ec2, so their copy is retired and04-ux/08-component-spec.md§2.5 no longer describes them.04-ux/06-settings-ia.mdstates that speech is not a Settings surface.
Consequences
- A user can no longer configure a transcription or speech provider from the app. The host capability remains reachable for plugins, for IPC callers, and for bindings already stored, but the desktop offers no UI to create one.
- Removing the renderer's only speech consumer also removes its use of the protocol catalog:
speech/getStatusis now called by no renderer module, while the IPC channel and its contract stay. - The renderer sheds the card, its styles, its search keywords, and its locale keys in eight catalogs; no host, protocol, permission, or schema change accompanies it.
- The user-visible speech vocabulary (Whisper, TTS, voices) no longer appears in the app shell at all, so the chat model picker is the only model surface.
Alternatives considered
Keep the card and hide it behind a developer flag
Rejected: a hidden surface still owns styles, locale keys, and a provider protocol catalog, and it would keep a second provider picker alive in an app whose speech consumers are plugins.
Remove the host capability with the UI
Rejected: the capability is a shipped contract with an ADR, an IPC surface, and a high-risk plugin permission. Removing it would break plugin adapters and invalidate stored AppSettings.speech bindings without a migration, for a surface the request only asked to remove from Settings.
Keep the card and ship the Composer actions
Rejected: the Composer controls were built and deliberately removed in the same release cycle (344ef4ec2, PR #555). Re-shipping them is a product decision that was already reversed, and the request is to remove the settings surface, not to grow the feature. The stale claims are corrected instead, so no spec describes a control that does not exist.