Summary
The sidebar Command Mode and Rewrite Mode pages start the shared ASRService directly, with no check that a file transcription is running and no dictation-intent claim. Two entry points therefore sit outside the arbitration that every other start path goes through.
// Sources/Fluid/Views/CommandModeView.swift:596
Task { await self.asr.start() }
// Sources/Fluid/Views/RewriteModeView.swift:265
Task { await self.asr.start() }
Repro
- Drop audio files so a transcription is running.
- Navigate to the Command Mode (or Rewrite Mode) page in the sidebar.
- Press the page's record button.
Both drive the shared ASR model concurrently. Either transcription can fail or return corrupted output.
Pre-existing, not introduced by #716
Both lines are unchanged on main at the same line numbers, and neither view is touched by #716. MeetingTranscriptionService.transcribeFile already used asrService.fileTranscriptionProvider on main, so the conflict was reachable before batching existed.
Filing it because #716 makes it materially easier to hit — a batch keeps the model busy far longer than a single file — and because #716 adds arbitration (FileTranscriptionSession, the isBatchTranscribing guards, the dictation-intent flag) that these two sites silently opt out of. Anyone reading that PR would reasonably assume the case is covered.
Fix
The straightforward version is the guard the hotkey paths already use, plus an intent claim so the batch's wait loop sees it:
guard !FileTranscriptionSession.isBatchTranscribing else { /* blocked feedback */ return }
The better version is what two independent reviews of #716 both recommended: a single canBeginRecording() gate used by every start site. The condition is currently duplicated across startRecording, beginDictationRecording, commandModeCallback and rewriteModeCallback in three different orderings, with four copies of notifyDictationBlockedByBatch(). That duplication is why these two sites were missed, and why a guard-ordering bug (fixed in #716, d2dd76d) was possible in the first place.
Note notifyDictationBlockedByBatch() is private to ContentView, so the shared gate needs it moved somewhere both views can reach.
Residual window
Even with the guard, a start racing a batch start can slip through between the check and asr.isRunning flipping true. That is the window beginDictationIntent() exists to close, so the sidebar sites should claim intent too — with matching release on every termination path, per the bug fixed in ebe47e7.
Summary
The sidebar Command Mode and Rewrite Mode pages start the shared
ASRServicedirectly, with no check that a file transcription is running and no dictation-intent claim. Two entry points therefore sit outside the arbitration that every other start path goes through.Repro
Both drive the shared ASR model concurrently. Either transcription can fail or return corrupted output.
Pre-existing, not introduced by #716
Both lines are unchanged on
mainat the same line numbers, and neither view is touched by #716.MeetingTranscriptionService.transcribeFilealready usedasrService.fileTranscriptionProvideronmain, so the conflict was reachable before batching existed.Filing it because #716 makes it materially easier to hit — a batch keeps the model busy far longer than a single file — and because #716 adds arbitration (
FileTranscriptionSession, theisBatchTranscribingguards, the dictation-intent flag) that these two sites silently opt out of. Anyone reading that PR would reasonably assume the case is covered.Fix
The straightforward version is the guard the hotkey paths already use, plus an intent claim so the batch's wait loop sees it:
The better version is what two independent reviews of #716 both recommended: a single
canBeginRecording()gate used by every start site. The condition is currently duplicated acrossstartRecording,beginDictationRecording,commandModeCallbackandrewriteModeCallbackin three different orderings, with four copies ofnotifyDictationBlockedByBatch(). That duplication is why these two sites were missed, and why a guard-ordering bug (fixed in #716, d2dd76d) was possible in the first place.Note
notifyDictationBlockedByBatch()isprivatetoContentView, so the shared gate needs it moved somewhere both views can reach.Residual window
Even with the guard, a start racing a batch start can slip through between the check and
asr.isRunningflipping true. That is the windowbeginDictationIntent()exists to close, so the sidebar sites should claim intent too — with matching release on every termination path, per the bug fixed in ebe47e7.