Skip to content

fix(chat): restore capability-gated voice input - #291

Merged
alwaysmavs merged 2 commits into
mainfrom
restore-voice-input
Jul 31, 2026
Merged

fix(chat): restore capability-gated voice input#291
alwaysmavs merged 2 commits into
mainfrom
restore-voice-input

Conversation

@alwaysmavs

Copy link
Copy Markdown
Contributor

Summary

Restore voice recording and transcription controls in the chat composer for signed-in OOMOL users.
The microphone button is now driven by the existing runtime voice capability, while local and BYOK
runtime modes continue to hide the OOMOL-only voice feature.

Issue and user impact

Voice recording, WAV encoding, transcription, and error handling remained in the codebase, but the
composer stopped rendering any entry point for them. As a result, signed-in users could no longer
start voice input even though their runtime advertised voice: true and the authenticated ASR
service remained available.

Root cause

The local-runtime foundation removed the voice integration from the shared composer to keep the
OOMOL ASR service out of anonymous local mode. The runtime capability model introduced a dedicated
voice flag at the same time, but that flag was never propagated to the composer. This turned an
intended local-mode restriction into a global UI removal.

Fix

  • Propagate runtimeCapabilities.voice from AppShell through ChatArea to ChatComposer.
  • Reconnect the existing recorder and transcription hook to the composer draft.
  • Restore the microphone trigger, preparation state, waveform, duration, stop, cancel, retry, and
    transcription-loading controls.
  • Restore microphone permission, no-speech, and transcription failure notices.
  • Cancel active voice work if the runtime loses voice capability.
  • Keep the voice entry point hidden when the runtime capability is disabled.
  • Add regression coverage for capability-gated microphone rendering.

Validation

  • pnpm run lint
  • pnpm run format
  • pnpm run build
  • pnpm test — 282 test files, 2122 tests passed
  • Voice-focused tests — 7 test files, 31 tests passed

@alwaysmavs
alwaysmavs marked this pull request as ready for review July 31, 2026 06:53
@BlackHole1

Copy link
Copy Markdown
Member

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 623cf5c3-1c4e-487c-a7b0-aba499006352

📥 Commits

Reviewing files that changed from the base of the PR and between b16eab2 and c5056c1.

📒 Files selected for processing (6)
  • src/i18n/app-messages.en.ts
  • src/i18n/app-messages.zh.ts
  • src/routes/Chat/ChatComposer.tsx
  • src/routes/Chat/ComposerModeControls.test.ts
  • src/routes/Chat/ComposerTrailingControls.test.ts
  • src/routes/Chat/ComposerTrailingControls.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/routes/Chat/ComposerModeControls.test.ts
  • src/routes/Chat/ChatComposer.tsx

Summary by CodeRabbit

  • New Features

    • Added optional voice input to the chat composer when supported by the runtime.
    • Recordings display activity, duration, and transcription progress.
    • Transcribed speech is inserted into the message draft.
    • Added controls to start, stop, cancel, retry, and recover from voice input errors.
    • The composer is temporarily disabled during voice activity, with localized discard messaging.
  • Tests

    • Added coverage for voice control visibility and recording-state behavior.

Walkthrough

The chat shell passes runtime voice capability to ChatArea and ChatComposer. ChatComposer records voice input, inserts transcriptions into the draft, disables editing during processing, and displays voice errors. Composer controls provide microphone, waveform, duration, retry, cancel, discard, and stop interactions. Localization and tests cover the new voice states.

Sequence Diagram(s)

sequenceDiagram
  participant AppShell
  participant ChatArea
  participant ChatComposer
  participant VoiceInput
  participant ComposerControls
  AppShell->>ChatArea: Pass runtime voice capability
  ChatArea->>ChatComposer: Pass voiceEnabled
  ChatComposer->>VoiceInput: Initialize voice handling
  ComposerControls->>ChatComposer: Start or stop voice input
  VoiceInput-->>ChatComposer: Return transcription or error
  ChatComposer->>ChatComposer: Update draft or display error
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the change and validation, but it omits the required Safety and Compatibility section and several verification items. Add the complete Verification and Safety and Compatibility sections from the template, including checklist status and runtime/UI verification.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the required format and clearly describes the capability-gated voice input change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch restore-voice-input

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

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/routes/Chat/ComposerModeControls.test.ts (1)

37-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Derive the expected label from the translation function.

The assertions hard-code the English string. A copy change in the chat.voiceInput locale entry then breaks the test for an unrelated reason. Build the expected attribute from t("chat.voiceInput").

♻️ Proposed refactor
 describe("ComposerModeControls", () => {
+  const voiceLabel = `aria-label="${t("chat.voiceInput")}"`
+
   it("shows voice input when the runtime enables voice", () => {
-    expect(renderControls(true)).toContain('aria-label="Voice input"')
+    expect(renderControls(true)).toContain(voiceLabel)
   })
 
   it("hides voice input when the runtime disables voice", () => {
-    expect(renderControls(false)).not.toContain('aria-label="Voice input"')
+    expect(renderControls(false)).not.toContain(voiceLabel)
   })
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/Chat/ComposerModeControls.test.ts` around lines 37 - 45, Update
the assertions in the ComposerModeControls tests to derive the expected
aria-label from the translation function using t("chat.voiceInput") instead of
hard-coding “Voice input”; preserve the existing enabled and disabled visibility
checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/routes/Chat/ComposerTrailingControls.tsx`:
- Around line 200-203: Gate all voice state by voiceEnabled: in
src/routes/Chat/ComposerTrailingControls.tsx lines 200-203, only derive
visibleVoiceError from voiceError or voiceRecorderError when voiceEnabled is
true; in src/routes/Chat/ChatComposer.tsx lines 749-757, gate voiceError,
voiceRecorderError, voiceRetryBlob, voiceStarting, and voiceTranscribing the
same way as voiceActive so composerVoiceControlMode returns idle and no voice
controls render when voice is disabled.

---

Nitpick comments:
In `@src/routes/Chat/ComposerModeControls.test.ts`:
- Around line 37-45: Update the assertions in the ComposerModeControls tests to
derive the expected aria-label from the translation function using
t("chat.voiceInput") instead of hard-coding “Voice input”; preserve the existing
enabled and disabled visibility checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b4b91698-f174-4fd7-8fa1-c9ab83e0b8dc

📥 Commits

Reviewing files that changed from the base of the PR and between 19225ea and b16eab2.

📒 Files selected for processing (6)
  • src/components/app-shell/AppShell.tsx
  • src/routes/Chat/ChatComposer.tsx
  • src/routes/Chat/ComposerModeControls.test.ts
  • src/routes/Chat/ComposerModeControls.tsx
  • src/routes/Chat/ComposerTrailingControls.tsx
  • src/routes/Chat/index.tsx

Comment thread src/routes/Chat/ComposerTrailingControls.tsx Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/routes/Chat/ComposerTrailingControls.tsx (1)

76-99: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Move the layout and style reads out of the per-frame draw path.

The draw effect depends on bars, so it runs on every audio frame while recording. Each run calls getBoundingClientRect() and getComputedStyle(). Both force style and layout work in the browser. The size and the resolved color only change when sizeRevision or the theme changes.

Cache the measured width and the fill color in a separate effect keyed on sizeRevision, then read the cached values in the bars effect.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/Chat/ComposerTrailingControls.tsx` around lines 76 - 99, Separate
layout and style reads from the bars drawing effect: add a size/style
measurement effect keyed by sizeRevision (and the relevant theme dependency)
that caches the canvas width and resolved color, then update the bars effect to
use those cached values instead of calling getBoundingClientRect() and
getComputedStyle() per frame. Preserve the existing canvas sizing and drawing
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/routes/Chat/ComposerTrailingControls.tsx`:
- Around line 232-264: The two voice controls currently share the same
accessible name during starting and transcribing states. Update the trailing
discard button’s aria-label in the ComposerTrailingControls voice controls to
use a distinct discard translation key such as chat.voiceDiscard, while
preserving chat.voiceCancel for the spinner/cancel control.

---

Nitpick comments:
In `@src/routes/Chat/ComposerTrailingControls.tsx`:
- Around line 76-99: Separate layout and style reads from the bars drawing
effect: add a size/style measurement effect keyed by sizeRevision (and the
relevant theme dependency) that caches the canvas width and resolved color, then
update the bars effect to use those cached values instead of calling
getBoundingClientRect() and getComputedStyle() per frame. Preserve the existing
canvas sizing and drawing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f2f85dd-336a-4b0d-801f-7a31837d7c41

📥 Commits

Reviewing files that changed from the base of the PR and between 19225ea and b16eab2.

📒 Files selected for processing (6)
  • src/components/app-shell/AppShell.tsx
  • src/routes/Chat/ChatComposer.tsx
  • src/routes/Chat/ComposerModeControls.test.ts
  • src/routes/Chat/ComposerModeControls.tsx
  • src/routes/Chat/ComposerTrailingControls.tsx
  • src/routes/Chat/index.tsx

Comment thread src/routes/Chat/ComposerTrailingControls.tsx
@alwaysmavs

Copy link
Copy Markdown
Contributor Author

Addressed the remaining CodeRabbit feedback in c5056c1: capability-gated stale voice state, distinct cancel/discard accessible labels with localized copy, cached waveform layout/style measurements outside the audio update path, and translation-derived test assertions. Added component regressions for the disabled stale-error and accessibility cases. Local lint, format, build, and the full 283-file/2124-test suite pass.

@alwaysmavs
alwaysmavs merged commit 64fc23b into main Jul 31, 2026
3 checks passed
@alwaysmavs
alwaysmavs deleted the restore-voice-input branch July 31, 2026 07:45
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.

2 participants