refactor mobile fire-and-forget calls

Mark intentional async work in the mobile screen with the void operator so lint can distinguish real promise bugs from deliberate fire-and-forget behavior.
pull/19545/head
Ryan Vogel 2026-03-30 08:30:28 -04:00
parent df3276fc87
commit 49b40e3c90
1 changed files with 48 additions and 48 deletions

View File

@ -713,7 +713,7 @@ export default function DictationScreen() {
useEffect(() => { useEffect(() => {
let mounted = true let mounted = true
;(async () => { void (async () => {
try { try {
const data = await FileSystem.readAsStringAsync(SERVER_STATE_FILE) const data = await FileSystem.readAsStringAsync(SERVER_STATE_FILE)
if (!mounted || !data) return if (!mounted || !data) return
@ -740,7 +740,7 @@ export default function DictationScreen() {
useEffect(() => { useEffect(() => {
let mounted = true let mounted = true
;(async () => { void (async () => {
let complete = false let complete = false
try { try {
@ -768,7 +768,7 @@ export default function DictationScreen() {
} }
if (complete) { if (complete) {
FileSystem.writeAsStringAsync(ONBOARDING_STATE_FILE, JSON.stringify({ completed: true })).catch(() => {}) void FileSystem.writeAsStringAsync(ONBOARDING_STATE_FILE, JSON.stringify({ completed: true })).catch(() => {})
} }
} }
@ -786,7 +786,7 @@ export default function DictationScreen() {
useEffect(() => { useEffect(() => {
if (!restoredRef.current) return if (!restoredRef.current) return
const payload = toSaved(servers, activeServerId, activeSessionId) const payload = toSaved(servers, activeServerId, activeSessionId)
FileSystem.writeAsStringAsync(SERVER_STATE_FILE, JSON.stringify(payload)).catch(() => {}) void FileSystem.writeAsStringAsync(SERVER_STATE_FILE, JSON.stringify(payload)).catch(() => {})
}, [activeServerId, activeSessionId, servers]) }, [activeServerId, activeSessionId, servers])
useEffect(() => { useEffect(() => {
@ -858,7 +858,7 @@ export default function DictationScreen() {
// Set up audio session and check microphone permissions on mount. // Set up audio session and check microphone permissions on mount.
useEffect(() => { useEffect(() => {
;(async () => { void (async () => {
try { try {
AudioManager.setAudioSessionOptions({ AudioManager.setAudioSessionOptions({
iosCategory: "playAndRecord", iosCategory: "playAndRecord",
@ -986,7 +986,7 @@ export default function DictationScreen() {
useEffect(() => { useEffect(() => {
let mounted = true let mounted = true
;(async () => { void (async () => {
await FileSystem.makeDirectoryAsync(WHISPER_MODELS_DIR, { intermediates: true }).catch(() => {}) await FileSystem.makeDirectoryAsync(WHISPER_MODELS_DIR, { intermediates: true }).catch(() => {})
let nextDefaultModel: WhisperModelID = DEFAULT_WHISPER_MODEL let nextDefaultModel: WhisperModelID = DEFAULT_WHISPER_MODEL
@ -1033,7 +1033,7 @@ export default function DictationScreen() {
useEffect(() => { useEffect(() => {
if (!whisperRestoredRef.current) return if (!whisperRestoredRef.current) return
const payload: WhisperSavedState = { defaultModel: defaultWhisperModel, mode: transcriptionMode } const payload: WhisperSavedState = { defaultModel: defaultWhisperModel, mode: transcriptionMode }
FileSystem.writeAsStringAsync(WHISPER_SETTINGS_FILE, JSON.stringify(payload)).catch(() => {}) void FileSystem.writeAsStringAsync(WHISPER_SETTINGS_FILE, JSON.stringify(payload)).catch(() => {})
}, [defaultWhisperModel, transcriptionMode]) }, [defaultWhisperModel, transcriptionMode])
useEffect(() => { useEffect(() => {
@ -1061,10 +1061,10 @@ export default function DictationScreen() {
whisperContextModelRef.current = null whisperContextModelRef.current = null
if (context) { if (context) {
context.release().catch(() => {}) void context.release().catch(() => {})
} }
releaseAllWhisper().catch(() => {}) void releaseAllWhisper().catch(() => {})
} }
}, []) }, [])
@ -1101,7 +1101,7 @@ export default function DictationScreen() {
useEffect(() => { useEffect(() => {
let active = true let active = true
;(async () => { void (async () => {
try { try {
if (Platform.OS !== "ios") return if (Platform.OS !== "ios") return
const existing = await Notifications.getPermissionsAsync() const existing = await Notifications.getPermissionsAsync()
@ -1334,7 +1334,7 @@ export default function DictationScreen() {
} }
finalizeRecordingState() finalizeRecordingState()
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {}) void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {})
} }
}, [ }, [
defaultWhisperModel, defaultWhisperModel,
@ -1350,7 +1350,7 @@ export default function DictationScreen() {
const stopRecording = useCallback(() => { const stopRecording = useCallback(() => {
if (!isRecordingRef.current && !isStartingRef.current) return if (!isRecordingRef.current && !isStartingRef.current) return
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}) void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {})
const baseAtStop = normalizeTranscriptSessions(baseTextRef.current) const baseAtStop = normalizeTranscriptSessions(baseTextRef.current)
const englishOnlyModel = isEnglishOnlyWhisperModel(defaultWhisperModel) const englishOnlyModel = isEnglishOnlyWhisperModel(defaultWhisperModel)
@ -1451,7 +1451,7 @@ export default function DictationScreen() {
const sendOutProgress = useSharedValue(0) const sendOutProgress = useSharedValue(0)
const handleClearTranscript = useCallback(() => { const handleClearTranscript = useCallback(() => {
Haptics.selectionAsync().catch(() => {}) void Haptics.selectionAsync().catch(() => {})
clearIconRotation.value = withSequence( clearIconRotation.value = withSequence(
withTiming(-30, { duration: 90 }), withTiming(-30, { duration: 90 }),
@ -1472,7 +1472,7 @@ export default function DictationScreen() {
}, [clearIconRotation, clearWaveform, sendOutProgress, stopRecording]) }, [clearIconRotation, clearWaveform, sendOutProgress, stopRecording])
const handleHideAgentState = useCallback(() => { const handleHideAgentState = useCallback(() => {
Haptics.selectionAsync().catch(() => {}) void Haptics.selectionAsync().catch(() => {})
setAgentStateDismissed(true) setAgentStateDismissed(true)
}, []) }, [])
@ -1488,7 +1488,7 @@ export default function DictationScreen() {
}, [clearWaveform, stopRecording]) }, [clearWaveform, stopRecording])
const handleOpenWhisperSettings = useCallback(() => { const handleOpenWhisperSettings = useCallback(() => {
Haptics.selectionAsync().catch(() => {}) void Haptics.selectionAsync().catch(() => {})
setDropdownMode("none") setDropdownMode("none")
setWhisperSettingsOpen(true) setWhisperSettingsOpen(true)
}, []) }, [])
@ -1497,7 +1497,7 @@ export default function DictationScreen() {
async (modelID: WhisperModelID) => { async (modelID: WhisperModelID) => {
const ok = await downloadWhisperModel(modelID) const ok = await downloadWhisperModel(modelID)
if (ok) { if (ok) {
Haptics.selectionAsync().catch(() => {}) void Haptics.selectionAsync().catch(() => {})
} }
}, },
[downloadWhisperModel], [downloadWhisperModel],
@ -1513,7 +1513,7 @@ export default function DictationScreen() {
await ensureWhisperModelReady(modelID) await ensureWhisperModelReady(modelID)
setDefaultWhisperModel(modelID) setDefaultWhisperModel(modelID)
setWhisperError("") setWhisperError("")
Haptics.selectionAsync().catch(() => {}) void Haptics.selectionAsync().catch(() => {})
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : "Unable to switch Whisper model" const message = error instanceof Error ? error.message : "Unable to switch Whisper model"
setWhisperError(message) setWhisperError(message)
@ -1557,7 +1557,7 @@ export default function DictationScreen() {
} }
} }
Haptics.selectionAsync().catch(() => {}) void Haptics.selectionAsync().catch(() => {})
}, },
[ [
activeWhisperModel, activeWhisperModel,
@ -1740,21 +1740,21 @@ export default function DictationScreen() {
setMonitorStatus(formatMonitorEventLabel(eventType)) setMonitorStatus(formatMonitorEventLabel(eventType))
if (eventType === "permission") { if (eventType === "permission") {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning).catch(() => {}) void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning).catch(() => {})
return return
} }
if (eventType === "complete") { if (eventType === "complete") {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}) void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {})
completePlayer.seekTo(0) void completePlayer.seekTo(0)
completePlayer.play() void completePlayer.play()
stopForegroundMonitor() stopForegroundMonitor()
setMonitorJob(null) setMonitorJob(null)
void loadLatestAssistantResponse(job.opencodeBaseURL, job.sessionID) void loadLatestAssistantResponse(job.opencodeBaseURL, job.sessionID)
return return
} }
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {}) void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {})
stopForegroundMonitor() stopForegroundMonitor()
setMonitorJob(null) setMonitorJob(null)
}, },
@ -1770,7 +1770,7 @@ export default function DictationScreen() {
const base = job.opencodeBaseURL.replace(/\/+$/, "") const base = job.opencodeBaseURL.replace(/\/+$/, "")
;(async () => { void (async () => {
try { try {
const response = await expoFetch(`${base}/event`, { const response = await expoFetch(`${base}/event`, {
signal: abortController.signal, signal: abortController.signal,
@ -1906,12 +1906,12 @@ export default function DictationScreen() {
setMonitorStatus("Monitoring (foreground only)") setMonitorStatus("Monitoring (foreground only)")
} }
sendPlayer.seekTo(0) void sendPlayer.seekTo(0)
sendPlayer.play() void sendPlayer.play()
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy).catch(() => {}) void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy).catch(() => {})
setTimeout(() => { setTimeout(() => {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}) void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {})
}, 70) }, 70)
sendOutProgress.value = withTiming( sendOutProgress.value = withTiming(
@ -1928,7 +1928,7 @@ export default function DictationScreen() {
) )
} catch { } catch {
setMonitorStatus("Failed to send prompt") setMonitorStatus("Failed to send prompt")
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {}) void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {})
setIsSending(false) setIsSending(false)
sendOutProgress.value = 0 sendOutProgress.value = 0
} }
@ -1951,9 +1951,9 @@ export default function DictationScreen() {
if (isRecordingRef.current) return if (isRecordingRef.current) return
setDropdownMode("none") setDropdownMode("none")
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}) void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {})
isHoldingRef.current = true isHoldingRef.current = true
startRecording() void startRecording()
}, [startRecording]) }, [startRecording])
const handlePressOut = useCallback(() => { const handlePressOut = useCallback(() => {
@ -2332,7 +2332,7 @@ export default function DictationScreen() {
const refreshAllServerHealth = useCallback(() => { const refreshAllServerHealth = useCallback(() => {
const ids = serversRef.current.map((s) => s.id) const ids = serversRef.current.map((s) => s.id)
ids.forEach((id) => { ids.forEach((id) => {
refreshServerStatusAndSessions(id, false) void refreshServerStatusAndSessions(id, false)
}) })
}, [refreshServerStatusAndSessions]) }, [refreshServerStatusAndSessions])
@ -2439,8 +2439,8 @@ export default function DictationScreen() {
} }
if (payload.eventType === "complete" && source === "received") { if (payload.eventType === "complete" && source === "received") {
completePlayer.seekTo(0) void completePlayer.seekTo(0)
completePlayer.play() void completePlayer.play()
} }
if ( if (
@ -2521,7 +2521,7 @@ export default function DictationScreen() {
}, [appState, syncSessionState]) }, [appState, syncSessionState])
const toggleServerMenu = useCallback(() => { const toggleServerMenu = useCallback(() => {
Haptics.selectionAsync().catch(() => {}) void Haptics.selectionAsync().catch(() => {})
setDropdownMode((prev) => { setDropdownMode((prev) => {
const next = prev === "server" ? "none" : "server" const next = prev === "server" ? "none" : "server"
if (next === "server") { if (next === "server") {
@ -2536,8 +2536,8 @@ export default function DictationScreen() {
const toggleSessionMenu = useCallback(() => { const toggleSessionMenu = useCallback(() => {
if (!activeServer || activeServer.status !== "online") return if (!activeServer || activeServer.status !== "online") return
Haptics.selectionAsync().catch(() => {}) void Haptics.selectionAsync().catch(() => {})
refreshServerStatusAndSessions(activeServer.id) void refreshServerStatusAndSessions(activeServer.id)
setDropdownRenderMode("session") setDropdownRenderMode("session")
setDropdownMode((prev) => (prev === "session" ? "none" : "session")) setDropdownMode((prev) => (prev === "session" ? "none" : "session"))
}, [activeServer, refreshServerStatusAndSessions]) }, [activeServer, refreshServerStatusAndSessions])
@ -2551,7 +2551,7 @@ export default function DictationScreen() {
setActiveServerId(id) setActiveServerId(id)
setActiveSessionId(null) setActiveSessionId(null)
setDropdownMode("none") setDropdownMode("none")
refreshServerStatusAndSessions(id) void refreshServerStatusAndSessions(id)
}, },
[refreshServerStatusAndSessions], [refreshServerStatusAndSessions],
) )
@ -2627,7 +2627,7 @@ export default function DictationScreen() {
setActiveServerId(existing.id) setActiveServerId(existing.id)
setActiveSessionId(null) setActiveSessionId(null)
setDropdownMode("none") setDropdownMode("none")
refreshServerStatusAndSessions(existing.id) void refreshServerStatusAndSessions(existing.id)
return true return true
} }
@ -2648,7 +2648,7 @@ export default function DictationScreen() {
setActiveServerId(id) setActiveServerId(id)
setActiveSessionId(null) setActiveSessionId(null)
setDropdownMode("none") setDropdownMode("none")
refreshServerStatusAndSessions(id) void refreshServerStatusAndSessions(id)
return true return true
}, },
[refreshServerStatusAndSessions], [refreshServerStatusAndSessions],
@ -2701,7 +2701,7 @@ export default function DictationScreen() {
const completeOnboarding = useCallback( const completeOnboarding = useCallback(
(openScanner: boolean) => { (openScanner: boolean) => {
setOnboardingComplete(true) setOnboardingComplete(true)
FileSystem.writeAsStringAsync(ONBOARDING_STATE_FILE, JSON.stringify({ completed: true })).catch(() => {}) void FileSystem.writeAsStringAsync(ONBOARDING_STATE_FILE, JSON.stringify({ completed: true })).catch(() => {})
if (openScanner) { if (openScanner) {
void handleStartScan() void handleStartScan()
@ -2720,7 +2720,7 @@ export default function DictationScreen() {
setLocalNetworkPermissionState("idle") setLocalNetworkPermissionState("idle")
setOnboardingReady(true) setOnboardingReady(true)
setOnboardingComplete(false) setOnboardingComplete(false)
FileSystem.deleteAsync(ONBOARDING_STATE_FILE, { idempotent: true }).catch(() => {}) void FileSystem.deleteAsync(ONBOARDING_STATE_FILE, { idempotent: true }).catch(() => {})
}, [permissionGranted]) }, [permissionGranted])
const connectPairPayload = useCallback( const connectPairPayload = useCallback(
@ -2735,7 +2735,7 @@ export default function DictationScreen() {
const pair = parsePair(rawData) const pair = parsePair(rawData)
if (!pair) { if (!pair) {
if (fromScan) { if (fromScan) {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {}) void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {})
setTimeout(() => { setTimeout(() => {
scanLockRef.current = false scanLockRef.current = false
}, 750) }, 750)
@ -2761,7 +2761,7 @@ export default function DictationScreen() {
} }
setScanOpen(false) setScanOpen(false)
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}) void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {})
}) })
.catch(() => { .catch(() => {
if (fromScan) { if (fromScan) {
@ -2820,9 +2820,9 @@ export default function DictationScreen() {
useEffect(() => { useEffect(() => {
if (!activeServerId) return if (!activeServerId) return
refreshServerStatusAndSessions(activeServerId) void refreshServerStatusAndSessions(activeServerId)
const timer = setInterval(() => { const timer = setInterval(() => {
refreshServerStatusAndSessions(activeServerId) void refreshServerStatusAndSessions(activeServerId)
}, 15000) }, 15000)
return () => clearInterval(timer) return () => clearInterval(timer)
}, [activeServerId, refreshServerStatusAndSessions]) }, [activeServerId, refreshServerStatusAndSessions])
@ -2859,7 +2859,7 @@ export default function DictationScreen() {
bundleId, bundleId,
}) })
Promise.allSettled( void Promise.allSettled(
list.map(async (server) => { list.map(async (server) => {
const secret = server.relaySecret.trim() const secret = server.relaySecret.trim()
const relay = server.relayURL const relay = server.relayURL
@ -2904,7 +2904,7 @@ export default function DictationScreen() {
count: list.length, count: list.length,
}) })
Promise.allSettled( void Promise.allSettled(
list.map(async (server) => { list.map(async (server) => {
const secret = server.relaySecret.trim() const secret = server.relaySecret.trim()
const relay = server.relayURL const relay = server.relayURL