fix(updater): stop the update prompt from blocking the main actor - #837
fix(updater): stop the update prompt from blocking the main actor#837nishantkumar1292 wants to merge 1 commit into
Conversation
The "Update Available" prompt used NSAlert.runModal(), which does not return until the alert is dismissed. It runs inside a main actor job, so every other @mainactor job queues behind it - including the dictation callbacks GlobalHotkeyManager posts from its event tap. FluidVoice is a menu bar app that is rarely frontmost, so the alert also came up unactivated behind other windows. The result is that dictation stops working the moment a release ships and stays dead until the user finds the hidden dialog. Present the offer from a floating, non-activating panel styled like the install status panel in SimpleUpdater. The main actor stays free, the prompt floats above the frontmost app without stealing focus, and the Install Now / Later behaviour is unchanged. Guard against duplicate panels, since the hourly check can now actually run while the prompt is on screen. Refs altic-dev#564, altic-dev#745 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The PR Policy check is blocking this PR because required template information is missing. Please update the PR description with:
Screenshots or video are required for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes. If this PR has no visual changes, check the no-visual-change box in the template. If this remains incomplete for 48 hours after opening, the PR may be closed. |
Greptile SummaryThe PR replaces the blocking automatic-update alert with a guarded, nonactivating floating panel so main-actor dictation callbacks remain responsive.
|
|
Friendly bump on this one. The reason I'd love a maintainer's eyes: this bug silently kills dictation for everyone on the current release whenever a new version ships — the update alert opens invisibly behind other windows and blocks the main actor, so the app looks dead until you stumble on the hidden dialog. It bit me twice in a single day, and the two earlier reports of it (#564, #745) were auto-closed as stale without a fix. The diff is small (+158/−14, one file) and reuses the existing |
|
@altic-dev please review, this is the main cause of crashes for me |
Description
The "Update Available" prompt is an
NSAlert.runModal()on the main actor.runModal()does not return until the alert is dismissed, and it runs inside a main actor job, so every
other
@MainActorjob queues behind it — including the dictation callbacksGlobalHotkeyManager.triggerDictationMode()posts from its CGEventTap thread(
GlobalHotkeyManager.swift:1843). FluidVoice is a menu bar app that is almost neverfrontmost, so the alert also comes up unactivated behind other windows.
Both halves together mean that when a release ships, dictation silently stops working and
stays dead until the user stumbles on the hidden dialog. The event tap keeps running on its
own thread, so the app still logs every hotkey press — it just can never act on one.
This PR presents the same offer from a floating, non-activating
NSPanelstyled like theinstall status panel in
SimpleUpdater.showUpdateInstallStatus(SimpleUpdater.swift:471).The main actor stays free, and
.floating+orderFrontRegardless()+[.canJoinAllSpaces, .fullScreenAuxiliary]puts the prompt above the frontmost app withoutstealing focus — so it is visible without interrupting an in-flight dictation (#745).
Install Now / Later semantics are unchanged: Install Now clears the snooze and runs the
existing manual update path, Later snoozes the version for 24 hours. Two small related
changes: a re-entrancy guard, because the hourly check can now actually run while the prompt
is on screen and would otherwise stack panels; and
NSApp.activate(ignoringOtherApps:)before the manual install call, because that path still reports failures through a modal
alert which must not end up hidden behind other windows.
Deadlock evidence
macOS 26.6 (25G70), FluidVoice 1.6.7 (build 18), Apple Silicon, prompt for v1.6.8.
sampleof the wedged process — 1744 of 1744 samples on the main thread:The main thread is inside a Swift concurrency job that never returns, which is why the modal
run loop keeps pumping AppKit events while every queued
@MainActorjob starves.Matching cutover in
~/Library/Logs/Fluid/Fluid.log:logged by the event tap, 30 matching "Transcription release stop deferred until recording
starts", and zero log lines from
ASRServiceorContentView— no main-actor work of anykind ran.
Every dead press looks like this, with nothing following it:
Reproduction
newer than the running build exists.
sample FluidVoiceshows the main thread parked inshowUpdateNotification→runModal.dictation immediately.
Type of Change
Related Issue or Discussion
Relates to #564 ("Update-check popup blocks app/hotkey access") and #745 ("Automatic update
prompt interrupts active dictation and can discard transcript"). Both were auto-closed as
stale rather than fixed; the root cause is still present on
main. Happy to open aDiscussion first if maintainers prefer that route — filing this with the thread sample and
logs attached since the cause is pinned to a specific line.
Testing
swiftlint --strict --config .swiftlint.yml Sourcesswiftformat --config .swiftformat SourcesVerification performed:
xcodebuild -project Fluid.xcodeproj -scheme Fluid -configuration Debug buildonmacOS 26.6 / Xcode 26.6 (Apple Silicon): BUILD SUCCEEDED, no new warnings.
--strictvia the same container image CI uses: 0 violations in 150 files.--lint: no changes required.level without activating the app (screenshots above), and Install Now correctly invokes the
existing manual update path.
Screenshots / Video
Captured from the patched Debug build on macOS 26.6 (Apple Silicon), 2x retina:
In context — floating above the frontmost window without activating the app:
Notes
showUpdateAlert(...)(manual "No Updates" / "Update Check Failed") still uses
runModal(); it is user-initiatedand the app is active at that point, and
installOfferedUpdate()now activates the appbefore invoking that path so those alerts cannot end up hidden. Converting the remaining
alerts is a reasonable follow-up.
mid-typing. Visibility comes from the floating window level instead.
UpdatePromptButtonoverrides
acceptsFirstMouseso a single click works while another app is frontmost. Theflip side is that a stray first click over the panel acts immediately; if that trade-off is
unwanted, dropping the override makes the first click focus the panel instead.
buttons, and the blank line made the panel unnecessarily tall). Easy to restore if
preferred.
🤖 Generated with Claude Code