Files
Codeman/docs/archive/voice-input-plan.md
T
arkonandClaude Opus 4.7 1c3f2f6571 docs: tighten CLAUDE.md and archive 22 completed plan docs
CLAUDE.md: fix stale counts (types 14 to 15, SSE events ~118 to ~120),
remove redundant footer sections (References list duplicated inline citations;
Common Workflows bullets were self-evident or already stated; Tunnel/Memory
Leak Prevention folded into neighboring sections). 251 to 234 lines.

Move 22 completed implementation/phase/audit plans to docs/archive/ via
git mv so history is preserved. Living reference docs remain in docs/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:14:07 +02:00

156 lines
7.0 KiB
Markdown

# 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();
```
2. **Fix `_showButtons()` to handle mobile** — Add mobile button selector:
```javascript
_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 = '';
}
```
3. **Fix cleanup leak** — Null out recognition instance:
```javascript
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
}
```
4. **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
5. **Reduce silence timeout** from 5s to 3s for snappier feel
6. **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
7. **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 |