From 73f564574f3b4cff0baf3441c5cc346db596129b Mon Sep 17 00:00:00 2001 From: ciregenz Date: Wed, 5 Aug 2026 15:54:23 -0700 Subject: [PATCH] [eric] dictation: with no cursor anywhere the transcript opens the composer typed-in, never dropped --- .../Dashboard/hooks/state/useDashboardController.ts | 11 +++++++++++ frontend/src/shared/voice/injectAtFocus.ts | 6 ++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts index 2c516c58..1ee53637 100644 --- a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts +++ b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts @@ -225,6 +225,17 @@ export function useDashboardController(dashboardId: string, isActive: boolean) { } }, [toolbarOpen, toolbarPrefill, toolbarPrefillMode]); + // Dictation with no field focused lands HERE instead of vanishing: the composer opens with the transcript typed in, unsent. + useEffect(() => { + if (!isActive) return; + const onDictation = (e: Event): void => { + const text = (e as CustomEvent).detail?.text; + if (typeof text === 'string' && text.trim()) handleStarter(text); + }; + window.addEventListener('openswarm:dictation-fallback', onDictation); + return () => window.removeEventListener('openswarm:dictation-fallback', onDictation); + }, [isActive, handleStarter]); + useDashboardClipboard({ isActive, dashboardId, diff --git a/frontend/src/shared/voice/injectAtFocus.ts b/frontend/src/shared/voice/injectAtFocus.ts index 00834139..54fd00ae 100644 --- a/frontend/src/shared/voice/injectAtFocus.ts +++ b/frontend/src/shared/voice/injectAtFocus.ts @@ -4,7 +4,7 @@ import { getWebview } from '@/shared/browserRegistry'; // Dictation lands where the user's cursor actually is, like every real dictation tool: a focused // in-app field gets the text typed in (undo-friendly, fires React input events), a focused browser // card forwards into the guest page's field, anything else falls back to the OS-level paste. -export type InjectTarget = 'field' | 'webview' | null; +export type InjectTarget = 'field' | 'webview' | 'composer' | null; export function injectAtFocus(text: string): InjectTarget { const active = document.activeElement as HTMLElement | null; @@ -40,5 +40,7 @@ export function injectAtFocus(text: string): InjectTarget { try { wv.focus?.(); void wv.insertText(text); return 'webview'; } catch { /* fall through */ } } } - return null; + // No cursor anywhere: open the dashboard composer with the transcript typed in. Words are never dropped. + window.dispatchEvent(new CustomEvent('openswarm:dictation-fallback', { detail: { text } })); + return 'composer'; }