Files
Codeman/docs/voice-input-plan.md
T
arkonandClaude Opus 4.6 3795c45cc1 chore: rename Claudeman to Codeman
Full product rename across 109 files (~834 occurrences):
- Env vars: CLAUDEMAN_* → CODEMAN_*
- Data dirs: ~/.claudeman/ → ~/.codeman/, ~/claudeman-cases/ → ~/codeman-cases/
- tmux prefix: claudeman- → codeman-
- localStorage: claudeman-* → codeman-*
- Package/CLI: claudeman → codeman
- GitHub repo: Ark0N/Claudeman → Ark0N/Codeman
- systemd service: claudeman-web → codeman-web
- Class: ClaudemanApp → CodemanApp

Migration infrastructure for seamless transition:
- state-store.ts: auto-migrates data directories on startup
- tmux-manager.ts: dual-prefix detection (legacy claudeman- sessions)
- app.js: localStorage key migration (preserves old keys)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 16:44:34 +01:00

7.0 KiB

Voice Input V2 — Implementation Plan

Executive Summary

Fix and improve the existing VoiceInput implementation. The core class is solid but has critical integration bugs that prevent it from working on mobile, plus several UX improvements needed to make it feel fast and polished.


Current State: What Exists

The VoiceInput singleton (app.js:602-830) is already committed and uses the Web Speech API with:

  • Toggle mode (tap start/stop), 5s silence auto-stop
  • interimResults: true for streaming transcription preview
  • iOS Safari isFinal workaround (750ms stability timer)
  • Desktop button in toolbar-right, mobile button in KeyboardAccessoryBar
  • voice-pulse CSS animation, .voice-preview overlay
  • Cleanup on SSE reconnect, haptic feedback on mobile

Critical Bugs Found (Must Fix)

Bug 1: Mobile button NEVER shows (CRITICAL)

KeyboardAccessoryBar.init() runs at line 2239, BEFORE VoiceInput.init() at line 2240. The accessory bar template checks VoiceInput.supported at render time — but init() hasn't run yet, so supported is still false. The inline style="${VoiceInput.supported ? '' : 'display:none'}" always resolves to display:none.

Fix: Move VoiceInput.init() BEFORE KeyboardAccessoryBar.init(), OR remove the inline style check and have VoiceInput.init() show/hide the mobile button after the fact (like it does for desktop).

Bug 2: _showButtons() ignores mobile button

_showButtons() only targets #voiceInputBtn (desktop). It never removes display:none from the mobile [data-action="voice"] button.

Fix: Add mobile button selector to _showButtons().

Bug 3: Recognition instance leak on cleanup

cleanup() stops recording and removes the preview element, but doesn't null out this.recognition. After cleanup() + init() on SSE reconnect, the old SpeechRecognition instance with its handlers is orphaned.

Fix: Add this.recognition = null in cleanup().

UX Improvements (Should Fix)

Improvement 1: Consider auto-sending after voice

Currently, voice text is inserted but the user must press Enter. This is safe but adds friction. Two options:

  • Option A (safe, current): Insert text, user presses Enter — good for a terminal where wrong commands matter
  • Option B (fast): Insert text + auto-send \r after a brief 500ms delay — feels more "voice assistant"-like
  • Recommendation: Keep Option A as default, but add an optional setting for auto-send

Improvement 2: Shorter silence timeout for commands

5 seconds of silence before auto-stop feels slow for short terminal commands. Consider:

  • 3 seconds for auto-stop (still generous for natural pauses)
  • Or make it configurable via settings

Improvement 3: Better visual state on mobile

The blue-tinted voice button in the accessory bar is distinctive but subtle. When recording:

  • The .recording class turns it red with pulse — good
  • But the button is small among other buttons — easy to miss the state change
  • Consider: also show a small red dot indicator in the header or terminal area during recording

Architecture Decision: Keep Web Speech API

Confirmed by research: Web Speech API is the right choice.

  • Free, fast (150-300ms interim), trivial complexity
  • Chrome + Safari = ~70% of users, ~95% of Codeman's target audience (devs on Chrome)
  • Works on localhost without HTTPS
  • Accuracy is adequate for English command dictation
  • Deepgram streaming (Phase 2 optional) only if accuracy complaints arise
  • Skip Whisper batch entirely (too slow for interactive voice input)

Implementation Plan

Phase 1: Fix Critical Bugs (Priority)

File: src/web/public/app.js

  1. Fix init order — Move VoiceInput.init() BEFORE KeyboardAccessoryBar.init():
// Current (broken):
KeyboardAccessoryBar.init();
VoiceInput.init();

// Fixed:
VoiceInput.init();
KeyboardAccessoryBar.init();
  1. Fix _showButtons() to handle mobile — Add mobile button selector:
_showButtons() {
  const desktopBtn = document.getElementById('voiceInputBtn');
  if (desktopBtn) desktopBtn.style.display = '';
  // Also show mobile button (may not exist yet if KeyboardAccessoryBar hasn't init'd)
  const mobileBtn = document.querySelector('[data-action="voice"]');
  if (mobileBtn) mobileBtn.style.display = '';
}
  1. Fix cleanup leak — Null out recognition instance:
cleanup() {
  if (this.isRecording) this.stop();
  if (this.previewEl) {
    this.previewEl.remove();
    this.previewEl = null;
  }
  this.recognition = null;  // <-- add this
  clearTimeout(this.silenceTimeout);
  clearTimeout(this._stabilityTimer);
  // ... rest
}
  1. Remove inline style from mobile button template — Since _showButtons() will handle visibility, the template should always render the button visible and let init() hide it if unsupported:
// Current (broken):
style="${VoiceInput.supported ? '' : 'display:none'}"

// Fixed: remove the style attr entirely, let _showButtons/_hideButtons manage it

Actually better: always show the button if we init VoiceInput before KeyboardAccessoryBar. The VoiceInput.supported will be set correctly by then.

Phase 2: UX Polish

  1. Reduce silence timeout from 5s to 3s for snappier feel

  2. Add recording indicator — When recording, add a subtle pulsing red dot to the session header or status area so the recording state is visible even if the button is off-screen

  3. Voice input setting — Add a toggle in App Settings to enable/disable voice input (some users may not want the button). Default: enabled on supported browsers.

Phase 3: Future Enhancements (Not in this PR)

  • Language selector (currently hardcoded en-US)
  • Auto-send option (insert text + \r automatically)
  • Deepgram WebSocket fallback for Firefox/Edge
  • Waveform visualization during recording
  • Voice command recognition ("clear", "compact", "new session")

Files to Modify

File Changes
src/web/public/app.js Fix init order, fix _showButtons(), fix cleanup(), remove inline style, reduce silence timeout
src/web/public/mobile.css (optional) Adjust voice preview positioning if needed

Testing Plan

  1. Desktop Chrome: Verify mic button visible in toolbar-right, click toggles recording state, interim text shows in preview, final text inserted at prompt
  2. Mobile Chrome (emulated): Verify mic button visible in accessory bar, tap toggles recording, pulse animation plays
  3. Firefox: Verify mic button is hidden (no SpeechRecognition support)
  4. SSE reconnect: Verify cleanup stops recording and re-init works
  5. No active session: Verify toast "No active session" shows when tapping mic with no session

Risk Assessment

Risk Impact Mitigation
iOS Safari isFinal bug Medium Already handled by 750ms stability timer
Chrome auto-stops after 60s Low Prompts are short; 3s silence timeout covers this
Mic permission denied Low Error toast with clear message
Init order regression High Integration test to verify button visibility