From 5ff1787f6d0aec9bb7a31ad0dcd6ef56a19a9d45 Mon Sep 17 00:00:00 2001 From: mssoftjp <115339791+mssoftjp@users.noreply.github.com> Date: Mon, 10 Nov 2025 00:54:36 +0900 Subject: [PATCH] fix: guard voice input UI promises --- src/plugin/VoiceInputPlugin.ts | 16 +++++++++++++--- src/views/VoiceInputView.ts | 8 ++++++-- src/views/VoiceInputViewActions.ts | 14 +++++++++----- src/views/VoiceInputViewUI.ts | 29 ++++++++++++++++++++--------- 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/plugin/VoiceInputPlugin.ts b/src/plugin/VoiceInputPlugin.ts index 17ac668..b3d1dfc 100644 --- a/src/plugin/VoiceInputPlugin.ts +++ b/src/plugin/VoiceInputPlugin.ts @@ -118,7 +118,9 @@ export default class VoiceInputPlugin extends Plugin { // Add ribbon icon this.addRibbonIcon('microphone', i18n.t('ui.titles.main'), () => { this.logger.debug('Ribbon icon clicked'); - void this.viewManager.activateVoiceInputView(); + void this.viewManager.activateVoiceInputView().catch((error) => { + this.logger.error('Failed to activate Voice Input view from ribbon', error); + }); }); this.logger.debug('Ribbon icon added'); @@ -128,7 +130,9 @@ export default class VoiceInputPlugin extends Plugin { name: i18n.t('ui.commands.openView'), callback: () => { this.logger.debug('Command executed: open-view'); - void this.viewManager.activateVoiceInputView(); + void this.viewManager.activateVoiceInputView().catch((error) => { + this.logger.error('Failed to activate Voice Input view from command', error); + }); } }); this.logger.debug('Commands registered'); @@ -153,7 +157,13 @@ export default class VoiceInputPlugin extends Plugin { } onunload(): void { - void this.teardownPlugin(); + void this.teardownPlugin().catch((error) => { + if (this.logger?.error) { + this.logger.error('Voice Input Plugin unload failed', error); + } else if (typeof console !== 'undefined' && console.error) { + console.error('Voice Input Plugin unload failed', error); + } + }); } private async teardownPlugin(): Promise { diff --git a/src/views/VoiceInputView.ts b/src/views/VoiceInputView.ts index a2e752c..c04a24c 100644 --- a/src/views/VoiceInputView.ts +++ b/src/views/VoiceInputView.ts @@ -42,7 +42,9 @@ export class VoiceInputView extends ItemView { } onOpen(): void { - void this.handleOpen(); + void this.handleOpen().catch((error) => { + console.error('Failed to open Voice Input view', error); + }); } private async handleOpen(): Promise { @@ -83,7 +85,9 @@ export class VoiceInputView extends ItemView { } onClose(): void { - void this.handleClose(); + void this.handleClose().catch((error) => { + console.error('Failed to close Voice Input view', error); + }); } private async handleClose(): Promise { diff --git a/src/views/VoiceInputViewActions.ts b/src/views/VoiceInputViewActions.ts index 2874443..7b1743a 100644 --- a/src/views/VoiceInputViewActions.ts +++ b/src/views/VoiceInputViewActions.ts @@ -213,7 +213,7 @@ export class VoiceInputViewActions { : 'max-duration'; this.pendingVadAutoStop = false; this.updateUIAfterStop(); - void this.processRecordedAudio(audioBlob, { type: reasonType }); + this.processRecordedAudio(audioBlob, { type: reasonType }); }; const handleMicrophoneStatusChange = (status: 'initializing' | 'ready' | 'error') => { @@ -303,7 +303,7 @@ export class VoiceInputViewActions { // Process audio for manual stop if (audioBlob && audioBlob.size > 0) { - await this.processRecordedAudio(audioBlob, stopReason); + this.processRecordedAudio(audioBlob, stopReason); } } catch (error) { @@ -378,7 +378,7 @@ export class VoiceInputViewActions { /** * Process recorded audio with proper notifications */ - private async processRecordedAudio(audioBlob: Blob, stopReason: StopReason): Promise { + private processRecordedAudio(audioBlob: Blob, stopReason: StopReason): void { // Show appropriate status based on stop reason if (this.view.ui.statusEl) { switch (stopReason.type) { @@ -406,7 +406,9 @@ export class VoiceInputViewActions { // Process queue if not already processing if (!this.isProcessingAudio) { - void this.processQueue(); + void this.processQueue().catch((error) => { + this.logger.error('Failed to process audio queue', error); + }); } } @@ -452,7 +454,9 @@ export class VoiceInputViewActions { if (this.recordingState.processingQueue.length > 0) { // Use setTimeout to avoid potential stack overflow setTimeout(() => { - void this.processQueue(); + void this.processQueue().catch((error) => { + this.logger.error('Failed to continue processing audio queue', error); + }); }, 0); } } diff --git a/src/views/VoiceInputViewUI.ts b/src/views/VoiceInputViewUI.ts index d00560d..1cb994a 100644 --- a/src/views/VoiceInputViewUI.ts +++ b/src/views/VoiceInputViewUI.ts @@ -121,7 +121,7 @@ export class VoiceInputViewUI { }); this.clipboardButton.setAttribute('aria-label', this.i18n.t('ui.tooltips.copy')); this.clipboardButton.addEventListener('click', () => { - void this.view.actions.copyToClipboard(); + this.runAction(() => this.view.actions.copyToClipboard(), 'Failed to copy text to clipboard'); }); // Clear button @@ -150,7 +150,7 @@ export class VoiceInputViewUI { }); this.insertAtCursorButton.setAttribute('aria-label', this.i18n.t('ui.tooltips.insertAtCursor')); this.insertAtCursorButton.addEventListener('click', () => { - void this.view.actions.insertToNote(); + this.runAction(() => this.view.actions.insertToNote(), 'Failed to insert text at cursor'); }); // Append button @@ -163,7 +163,7 @@ export class VoiceInputViewUI { }); this.appendButton.setAttribute('aria-label', this.i18n.t('ui.tooltips.append')); this.appendButton.addEventListener('click', () => { - void this.view.actions.appendToNote(); + this.runAction(() => this.view.actions.appendToNote(), 'Failed to append text to note'); }); // Keep the old insertButton for backward compatibility (set to hidden) @@ -178,7 +178,7 @@ export class VoiceInputViewUI { cls: 'voice-input-cancel-button-full voice-input-hidden' }); this.cancelButton.addEventListener('click', () => { - void this.view.actions.cancelRecording(); + this.runAction(() => this.view.actions.cancelRecording(), 'Failed to cancel recording'); }); // Second row - Record button container @@ -267,7 +267,7 @@ export class VoiceInputViewUI { return; } - void this.view.actions.toggleRecording(); + this.runAction(() => this.view.actions.toggleRecording(), 'Failed to toggle recording'); }; // Push-to-talk functionality with delayed start @@ -386,13 +386,15 @@ export class VoiceInputViewUI { */ updateSettingsUI() { // Update correction toggle - if (this.correctionToggle) { - this.correctionToggle.setValue(this.plugin.settings.enableTranscriptionCorrection); + const correctionToggle = this.correctionToggle; + if (correctionToggle) { + correctionToggle.setValue(this.plugin.settings.enableTranscriptionCorrection); } // Update transcription model dropdown - if (this.transcriptionModelDropdown) { - this.transcriptionModelDropdown.setValue(this.plugin.settings.transcriptionModel); + const transcriptionModelDropdown = this.transcriptionModelDropdown; + if (transcriptionModelDropdown) { + transcriptionModelDropdown.setValue(this.plugin.settings.transcriptionModel); } // (no additional post-processing UI elements to update) @@ -416,6 +418,15 @@ export class VoiceInputViewUI { this.textArea.addEventListener('cut', this.textChangeHandler); } + /** + * Helper to safely run async view actions without unhandled rejections + */ + private runAction(task: () => Promise, context: string): void { + void task().catch((error) => { + console.error(context, error); + }); + } + /** * Clean up resources */