mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
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>
156 lines
7.0 KiB
Markdown
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 |
|