diff --git a/mobile/app/h/[hostId]/session/[worktreeId].tsx b/mobile/app/h/[hostId]/session/[worktreeId].tsx index 6a7bfc3a54f..bf3b828e68f 100644 --- a/mobile/app/h/[hostId]/session/[worktreeId].tsx +++ b/mobile/app/h/[hostId]/session/[worktreeId].tsx @@ -1195,6 +1195,16 @@ export default function SessionScreen() { const { toggleTabChatView, showNativeChat, showNativeChatRef } = nativeChatController nativeChatSendError.bannerMountedRef.current = showNativeChat + function handleDictationError(err: unknown) { + const error = err instanceof Error ? err : new Error(String(err)) + if (isDictationSetupRequiredError(error.message)) { + setShowDictationSetup(true) + return + } + triggerError() + showToast(error.message) + } + const dictation = useMobileDictation({ client, enabled: canSend, @@ -1236,13 +1246,7 @@ export default function SessionScreen() { }, onError: (err) => { dictationRouteContextRef.current = null - // Dictation not set up on desktop → open the setup sheet instead of a dead-end toast. - if (isDictationSetupRequiredError(err.message)) { - setShowDictationSetup(true) - return - } - triggerError() - showToast(err.message) + handleDictationError(err) } }) @@ -1255,10 +1259,9 @@ export default function SessionScreen() { if (dictationRouteContextRef.current === routeContext) { dictationRouteContextRef.current = null } - triggerError() - showToast(err instanceof Error ? err.message : String(err)) + handleDictationError(err) }) - }, [activeHandle, dictation, liveInputTerminalHandles, triggerError, showToast]) + }, [activeHandle, dictation, handleDictationError, liveInputTerminalHandles]) const cancelDictation = useCallback(() => { dictationRouteContextRef.current = null diff --git a/mobile/src/dictation/mobile-dictation-error-routing.test.ts b/mobile/src/dictation/mobile-dictation-error-routing.test.ts new file mode 100644 index 00000000000..f871a7fd9d7 --- /dev/null +++ b/mobile/src/dictation/mobile-dictation-error-routing.test.ts @@ -0,0 +1,34 @@ +import { readFileSync } from 'node:fs' +import { describe, expect, it } from 'vitest' + +const sessionRouteSource = readFileSync( + new URL('../../app/h/[hostId]/session/[worktreeId].tsx', import.meta.url), + 'utf8' +) + +function routeSlice(anchorStart: string, anchorEnd: string): string { + const start = sessionRouteSource.indexOf(anchorStart) + expect(start).toBeGreaterThanOrEqual(0) + const end = sessionRouteSource.indexOf(anchorEnd, start) + expect(end).toBeGreaterThan(start) + return sessionRouteSource.slice(start, end) +} + +describe('mobile dictation error routing', () => { + it('keeps setup tokens out of startup and in-flight error toasts', () => { + const owner = routeSlice( + 'function handleDictationError(err: unknown)', + 'const dictation = useMobileDictation({' + ) + expect(owner).toContain('isDictationSetupRequiredError(error.message)') + expect(owner).toContain('setShowDictationSetup(true)') + + const hook = routeSlice('const dictation = useMobileDictation({', 'const startDictation') + expect(hook).toContain('handleDictationError(err)') + + const start = routeSlice('const startDictation', 'const cancelDictation') + expect(start).toContain('handleDictationError(err)') + expect(start).not.toContain('showToast(') + expect(sessionRouteSource.match(/handleDictationError\(err\)/g)).toHaveLength(2) + }) +})