[fix] Let only the session on screen answer the push-to-talk chord [AGE-4239] - #6477
Conversation
… chord Holding Ctrl+Alt captured nothing, and the composer stayed in its dictation state after the message was sent. The chat panel keeps every visited session mounted behind display:none, and usePushToTalk binds its listener on the document, so one chord armed every mounted mic at once. Each opened its own SpeechRecognition, they fought over the single microphone, and the composer in view was as likely to lose as to win — which reads as dictation hearing nothing. The guard is the one the repo already uses for this, isOnScreen, checked when the chord arms and again when the arm delay elapses, so switching sessions mid-hold does not open the mic behind the switch either. The composer's lock now follows intent rather than the recogniser. The browser takes hundreds of milliseconds to seconds to close a session after stop(), and holding the editor read-only across that teardown blocks the edit-and-send the person released the chord to do. The editor's own dictation session still follows `active`, so the trailing final result still lands. A send now closes the mic. Sending mid-dictation left the recogniser running with nowhere to write — it stayed lit until it was stopped by hand, and every word after the send was discarded in silence. The mic leaves its stopper on the shared voice surface, the same seam it already reports dictation errors and the editor lock through, and both composers end dictation as they submit. Lifting the recogniser itself would re-render the whole composer on every interim word, which is why it stays in the button. A send also retires the editor's dictation session. The recogniser flushes a last result on the way out, which arrives after the composer has been emptied, and the session recreates its nodes on demand — so the words that just left as a message were written straight back into the empty box. clear() gets the same treatment for the same reason. Voice-message mode stays gated off; this is the dictation path the shortcut actually drives. Fixes #6452
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📘 Docs preview
This comment updates in place on every push. |
Website previewPreview URL: https://pr-6477-agenta-website-preview.mahmoud-637.workers.dev Built from |
📝 SummarySummary by CodeRabbit
WalkthroughThe PR adds Cloudflare canonical redirects, direct docs-worker routing, sitemap and production monitoring checks, and canonical build validation. It also updates chat dictation teardown, visible push-to-talk handling, composer submission, and related unit tests. ChangesWebsite delivery and docs monitoring
Voice dictation lifecycle
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: 🔵 Low · up to The PR adds production edge redirect configuration that depends on correctly scoped Cloudflare permissions and can leave the prior redirect state unrecovered if a later deployment or verification step fails. It is mergeable with explicit owner confirmation of token scope and rollback handling. Sequence Diagram(s)sequenceDiagram
participant ProductionWorkflow
participant configureCanonicalRedirect
participant Cloudflare
participant verifyCanonicalRedirects
ProductionWorkflow->>configureCanonicalRedirect: configure zone redirect
configureCanonicalRedirect->>Cloudflare: update canonical ruleset
ProductionWorkflow->>verifyCanonicalRedirects: verify redirect responses
verifyCanonicalRedirects->>Cloudflare: request canonical URLs
sequenceDiagram
participant Composer
participant VoiceInputButton
participant SpeechRecognition
participant RichChatInput
Composer->>VoiceInputButton: end dictation
VoiceInputButton->>SpeechRecognition: stop recognition
Composer->>RichChatInput: submit or clear
RichChatInput->>RichChatInput: retire dictation session
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
Full details: Linked Issues checkExplanation The changes fix shortcut-driven dictation and close the microphone after sending, but they do not implement or verify the linked issue's stated voice-message audio capture and sending behavior. The PR description states that voice-message mode remains disabled. [ Full details: Out of Scope Changes checkExplanation The website canonical-redirect scripts, sitemap changes, production workflow changes, documentation-monitor workflow, and related documentation are unrelated to the linked chat dictation issue. [ Full details: Docstring CoverageExplanation Docstring coverage is 25.00% which is insufficient. The required threshold is 60.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 15 files. (5 skipped: 5 unsupported.)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Team
Run ID: 0c4c9b08-9b6d-4801-93ea-74b65aa3946e
📒 Files selected for processing (20)
.github/workflows/16-website-production.yml.github/workflows/20-docs-monitor.ymldocs/README.mddocs/scripts/monitor-production.mjsdocs/wrangler.production.jsoncweb/mobile/src/features/chat/Composer.tsxweb/oss/src/components/AgentChatSlice/components/AgentComposerDock.tsxweb/packages/agenta-chat/src/components/VoiceInputButton.tsxweb/packages/agenta-chat/src/hooks/usePushToTalk.tsweb/packages/agenta-chat/src/hooks/useVoiceComposer.tsweb/packages/agenta-chat/tests/unit/components/voiceInputButtonPushToTalk.test.tsxweb/packages/agenta-chat/tests/unit/hooks/usePushToTalk.test.tsweb/packages/agenta-ui/src/RichChatInput/RichChatInput.tsxweb/packages/agenta-ui/tests/unit/richChatInputDictationSend.render.test.tsxwebsite/AGENTS.mdwebsite/astro.config.mjswebsite/scripts/configure-canonical-redirect.mjswebsite/scripts/configure-canonical-redirect.test.mjswebsite/scripts/verify-build.mjswebsite/scripts/verify-canonical-redirects.sh
Included review availability: Your plan provides up to 8 included reviews per hour; 4 remain after this review.
Context
Holding the push-to-talk chord (⌃⌥ / Ctrl+Alt) in the chat composer captured nothing, and the composer stayed in its recording state after the message was sent.
Three separate causes, all on the dictation path (voice-message mode is still gated off behind
VOICE_MESSAGE_MODE_ENABLED, so the shortcut only ever drives dictation):AgentChatPanelkeeps every visited session mounted behinddisplay: none, andusePushToTalkbinds its listener on thedocument. One chord armed every mounted mic at once. Each opened its ownSpeechRecognition, they fought over the single microphone, and the composer in view was as likely to lose as to win. That reads as dictation hearing nothing.activeflag, which trailsstop()by hundreds of milliseconds to seconds while the browser closes its speech socket. Releasing the chord left the editor locked across that whole teardown.Changes
Only the session on screen answers the chord.
usePushToTalktakes the mic's own root and checksisOnScreenbefore it arms, then rechecks when the arm delay elapses so switching sessions mid-hold does not open the mic behind the switch.isOnScreenis the helper the repo already uses for exactly this hazard (ApprovalCardguards its window-level key handler the same way).The editor lock follows intent, not the recogniser.
onDictatingChangenow reportstranscribe.recording, which flips the moment the chord is released. The editor's own dictation session still followsactive, so the recogniser's trailing final result still lands where it belongs.A send closes the mic. The mic leaves its stopper on
useVoiceComposer, the same shared voice surface it already reports dictation errors and the editor lock through, and both composers callendDictation()as they submit. The recogniser itself stays inside the button on purpose: lifting it would re-render the whole composer on every interim word.A send also retires the editor's dictation session.
submitEditorAsMarkdownempties the root but leftdictationRefalive, and the session recreates its nodes on demand. A trailing result arriving after the send therefore wrote the sent text straight back into the empty composer.clear()gets the same treatment for the same reason.Everything except the two composer call sites lives in
@agenta/chatand@agenta/ui, so desktop and/mare fixed together.Tests
Six new unit tests, each confirmed to fail with its fix reverted:
voiceInputButtonPushToTalk.test.tsx: a hidden session ignores the chord; one mic opens when a hidden session is mounted alongside a visible one; the composer unlocks on release without waiting out the teardown; the mic hands the composer a working stopper.usePushToTalk.test.ts: an off-screen session never arms, and a session switched away during the arm delay does not open the mic.richChatInputDictationSend.render.test.tsx: a trailing result after a send (and afterclear()) cannot refill the composer.Suites:
@agenta/ui176/176,@agenta/chat570 passing. The 8 failures intests/unit/state/sessionMessages.test.ts(localStorageundefined) predate this branch and are untouched by it.Verified against the running desktop app with a stubbed recogniser:
contenteditablewas back totrue150ms after release withonendstill 3 seconds away.stop()was called once, the composer emptied, and it stayed empty through the trailing result and the lateonend.What to QA
Fixes #6452