From 81ac1c3b407fd729be7f8688d48a0cc0c0692c48 Mon Sep 17 00:00:00 2001 From: Yurii Chukhlib Date: Sat, 8 Aug 2026 05:24:05 +0200 Subject: [PATCH] fix(typing): stop raising every window of the target app when restoring focus after dictation Drop .activateAllWindows from the focus-restore activation options in TypingService.activateApp(pid:); keep .activateIgnoringOtherApps so the target app is still brought forward without raising every window of a multi-window app (WebStorm, multi-window Xcode, browsers) on each dictation. Issue #748. The inline options literal is extracted into a named internal testable seam (focusRestoreActivationOptions) and a regression test is added to the existing TypingServiceTransientPasteboardTests asserting the option set contains .activateIgnoringOtherApps and not .activateAllWindows. Co-Authored-By: Claude --- Sources/Fluid/Services/TypingService.swift | 9 ++++++++- .../TypingServiceTransientPasteboardTests.swift | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Sources/Fluid/Services/TypingService.swift b/Sources/Fluid/Services/TypingService.swift index 6cacc699..b3315b7a 100644 --- a/Sources/Fluid/Services/TypingService.swift +++ b/Sources/Fluid/Services/TypingService.swift @@ -255,6 +255,13 @@ final class TypingService { return nil } + /// Activation options used to restore focus to the external target app after dictation. + /// `.activateAllWindows` is intentionally omitted: raising every window of a multi-window + /// app (e.g. WebStorm) destroys the user's window layout on each dictation (issue #748). + static let focusRestoreActivationOptions: NSApplication.ActivationOptions = [ + .activateIgnoringOtherApps, + ] + /// Best-effort: activates the app with the given PID, unless it's Fluid itself. @discardableResult static func activateApp(pid: pid_t) -> Bool { @@ -269,7 +276,7 @@ final class TypingService { return false } - return app.activate(options: [.activateAllWindows, .activateIgnoringOtherApps]) + return app.activate(options: Self.focusRestoreActivationOptions) } // MARK: - Public API diff --git a/Tests/FluidDictationIntegrationTests/TypingServiceTransientPasteboardTests.swift b/Tests/FluidDictationIntegrationTests/TypingServiceTransientPasteboardTests.swift index 32276672..aeedc61e 100644 --- a/Tests/FluidDictationIntegrationTests/TypingServiceTransientPasteboardTests.swift +++ b/Tests/FluidDictationIntegrationTests/TypingServiceTransientPasteboardTests.swift @@ -63,4 +63,20 @@ final class TypingServiceTransientPasteboardTests: XCTestCase { "ConcealedType signals sensitive content and must not be applied to a transcript" ) } + + func testFocusRestoreDoesNotRaiseAllWindowsOfTargetApp() { + // Issue #748: restoring focus after dictation must NOT raise every window of the + // target app. `.activateAllWindows` brings forward all windows of the process and + // destroys a multi-window layout (e.g. WebStorm) on every dictation. + let options = TypingService.focusRestoreActivationOptions + + XCTAssertTrue( + options.contains(.activateIgnoringOtherApps), + "focus restore must still activate the target app and bring it forward" + ) + XCTAssertFalse( + options.contains(.activateAllWindows), + "Restoring focus must not raise every window of the target app (issue #748)" + ) + } }