diff --git a/CLAUDE.md b/CLAUDE.md index 7f404bd8..8f0dcca1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,7 +35,7 @@ When user says "COM": 1. Increment version in BOTH `package.json` AND `CLAUDE.md` (verify they match with `grep version package.json && grep Version CLAUDE.md`) 2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web` -**Version**: 0.1640 (must match `package.json`) +**Version**: 0.1641 (must match `package.json`) ## Project Overview diff --git a/package.json b/package.json index e537b590..ad5338f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claudeman", - "version": "0.1640", + "version": "0.1641", "description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence", "type": "module", "main": "dist/index.js", diff --git a/src/web/public/app.js b/src/web/public/app.js index 77f2b489..7bfac3da 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -960,6 +960,7 @@ const VoiceInput = { }, _onWebSpeechResult(event) { + if (!this.isRecording) return; this._resetSilenceTimeout(); let interim = ''; let finalText = ''; @@ -1023,13 +1024,18 @@ const VoiceInput = { _insertText(text) { if (!app.activeSessionId || !text.trim()) return; const trimmed = text.trim(); - if (app._localEchoEnabled && app._localEchoOverlay) { - // Local echo on: inject into overlay so backspace/editing works (same as paste). - app._localEchoOverlay.appendText(trimmed); - setTimeout(() => { if (app.terminal) app.terminal.focus(); }, 150); - } else { - // Local echo off: show editable compose overlay before sending to PTY. + const mode = this._getDeepgramConfig().insertMode || 'direct'; + + if (mode === 'compose') { this._showComposeOverlay(trimmed); + } else { + // Direct mode: inject into local echo overlay if available, else send to PTY + if (app._localEchoEnabled && app._localEchoOverlay) { + app._localEchoOverlay.appendText(trimmed); + } else { + app.sendInput(trimmed).catch(() => {}); + } + setTimeout(() => { if (app.terminal) app.terminal.focus(); }, 150); } }, @@ -2585,14 +2591,29 @@ class ClaudemanApp { const settingsPromise = fetch('/api/settings').then(r => r.ok ? r.json() : null).catch(() => null); this.loadQuickStartCases(null, settingsPromise); this.setupEventListeners(); - // Mobile settings gear: non-passive touchstart to prevent keyboard dismiss - const settingsBtn = document.querySelector('.btn-settings-mobile'); - if (settingsBtn) { - settingsBtn.addEventListener('touchstart', (e) => { - e.preventDefault(); - e.stopPropagation(); - app.openAppSettings(); - }, { passive: false }); + // Mobile: ensure button taps register even when keyboard is visible. + // On mobile, tapping a button while the soft keyboard is up causes the + // browser to dismiss the keyboard first (blur event), swallowing the tap. + // The button only receives the click on a second tap. Fix: intercept + // touchstart on buttons while keyboard is visible, preventDefault to stop + // the dismiss-swallows-tap behavior, and trigger the click programmatically. + if (MobileDetection.isTouchDevice()) { + const addKeyboardTapFix = (container) => { + if (!container) return; + container.addEventListener('touchstart', (e) => { + if (!KeyboardHandler.keyboardVisible) return; + const btn = e.target.closest('button'); + if (!btn) return; + e.preventDefault(); + btn.click(); + // Refocus terminal so keyboard stays open (e.g. voice input button) + if (typeof app !== 'undefined' && app.terminal) { + app.terminal.focus(); + } + }, { passive: false }); + }; + addKeyboardTapFix(document.querySelector('.toolbar')); + addKeyboardTapFix(document.querySelector('.welcome-overlay')); } // System stats polling deferred until sessions exist (started in handleInit/session:created) // Setup online/offline detection @@ -10995,6 +11016,7 @@ class ClaudemanApp { document.getElementById('voiceDeepgramKey').value = voiceCfg.apiKey || ''; document.getElementById('voiceLanguage').value = voiceCfg.language || 'en-US'; document.getElementById('voiceKeyterms').value = voiceCfg.keyterms || 'claudeman, tmux, respawn, subagent, ralph, fastify, xterm, pty'; + document.getElementById('voiceInsertMode').value = voiceCfg.insertMode || 'direct'; // Reset key visibility to hidden const keyInput = document.getElementById('voiceDeepgramKey'); keyInput.type = 'password'; @@ -11190,6 +11212,7 @@ class ClaudemanApp { apiKey: document.getElementById('voiceDeepgramKey').value.trim(), language: document.getElementById('voiceLanguage').value, keyterms: document.getElementById('voiceKeyterms').value.trim(), + insertMode: document.getElementById('voiceInsertMode').value, }); // Save notification preferences separately diff --git a/src/web/public/index.html b/src/web/public/index.html index fb3ae6ad..f8cffcb6 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -1175,6 +1175,15 @@ — +
Behavior
+
+ Insert mode + +
+
Deepgram Nova-3