Skip to content

Fix main window stranded on unreachable Space in menu-bar-only mode#647

Open
shreeraman96 wants to merge 2 commits into
mainfrom
fix/646-main-window-space-reveal
Open

Fix main window stranded on unreachable Space in menu-bar-only mode#647
shreeraman96 wants to merge 2 commits into
mainfrom
fix/646-main-window-space-reveal

Conversation

@shreeraman96

@shreeraman96 shreeraman96 commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Description

The window server can leave the main window bound to another Space — including a defunct one from a previous session — where orderFrontRegardless() "succeeds" invisibly. In menu-bar-only mode (Hide from Dock & App Switcher) the user has no Dock icon or Cmd+Tab to recover it, so the app appears windowless: launch shows nothing, and menu bar → Settings... does nothing, while the log claims "Brought main window to front".

Fix: add NSWindow.revealOnActiveSpace() which inserts .moveToActiveSpace into the window's collection behavior before ordering front, so every reveal collects the window onto the Space the user is currently viewing. Used in both reveal paths: AppDelegate.bringMainWindowToFrontIfPresent() (launch/reopen/notification) and MenuBarManager.bringToFront(_:) (menu bar → Settings/openMainWindow).

Type of Change

  • 🐞 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 🧹 Chore
  • 📝 Documentation update

Related Issue or Discussion

Closes #646

Testing

  • Tested on Intel Mac
  • Tested on Apple Silicon Mac
  • Tested on macOS version: 26 (Darwin 25.5)
  • Ran linter locally: swiftlint --strict --config .swiftlint.yml Sources
  • Ran formatter locally: swiftformat --config .swiftformat Sources
  • Ran tests locally: FluidDictationIntegrationTests/WindowRevealTests (new regression test) — passed

Live verification: with the app in accessory mode, stranded the main window on another Space, then clicked menu bar → Settings... — the window is collected onto the current Space. Reproduced the pre-fix failure the same way.

Screenshots / Video

  • No UI/visual changes; screenshots/video are not applicable.

Notes

No pixel-level UI changes — the fix changes where the window appears (always the user's active Space). A screenshot cannot capture Space collection; the Testing section describes the live repro/verification instead. Side benefit: opening from the menu bar now brings the window to the user's current Space rather than relying on macOS to switch Spaces.

Reveal the main window with .moveToActiveSpace so it is collected onto
the user's current Space instead of being ordered front on a Space the
user cannot reach (no Dock icon or Cmd+Tab in accessory mode). Covers
launch reveal, reopen, and menu-bar Settings paths.

Fixes #646
@github-actions github-actions Bot added needs PR template Pull request is missing required template content. needs screenshots Pull request needs screenshot or video evidence. labels Jul 18, 2026
@github-actions

Copy link
Copy Markdown

The PR Policy check is blocking this PR because required template information is missing.

Please update the PR description with:

  • Description
  • Type of Change
  • Related Issue or Discussion
  • Testing
  • Screenshots / Video

Visual files detected:

  • Sources/Fluid/Services/MenuBarManager.swift
  • Sources/Fluid/UI/NSWindow+ActiveSpaceReveal.swift

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.

@github-actions github-actions Bot removed needs PR template Pull request is missing required template content. needs screenshots Pull request needs screenshot or video evidence. labels Jul 18, 2026
@greptile-apps

greptile-apps Bot commented Jul 18, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a bug where the main window can be stranded on an unreachable Mission Control Space in menu-bar-only (accessory) mode, leaving the app effectively windowless with no Dock icon or Cmd+Tab to recover it.

  • Introduces NSWindow.revealOnActiveSpace(), which temporarily inserts .moveToActiveSpace into the window's collection behavior before ordering front, then restores the original behavior on the next run-loop pass via DispatchQueue.main.async { [weak self] }.
  • Replaces the two direct orderFrontRegardless() + makeKeyAndOrderFront(nil) call sites (in AppDelegate and MenuBarManager) with the new helper, and adds a regression test in WindowRevealTests.swift that verifies both the synchronous behavior change and the asynchronous restore.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to window ordering, correctly restores state on the next run-loop pass, and is covered by a regression test.

The fix is a small, well-contained AppKit operation: insert a flag, order the window, restore the flag asynchronously. Both call sites are updated consistently, the [weak self] capture in the async block handles window lifetime correctly, and the test exercises both the synchronous mutation and the async restore. No unrelated behavior is changed.

No files require special attention.

Reviews (2): Last reviewed commit: "Scope .moveToActiveSpace to the reveal, ..." | Re-trigger Greptile

Comment thread Sources/Fluid/UI/NSWindow+ActiveSpaceReveal.swift
Comment on lines +1 to +5
import AppKit
@testable import FluidVoice_Debug
import XCTest

final class WindowRevealTests: XCTestCase {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test lives in the wrong target. WindowRevealTests is a focused unit test for window collection-behavior; it belongs in a general unit-test target, not FluidDictationIntegrationTests, which is set up for dictation end-to-end tests. Placing it here means the window regression guard only runs when the heavier integration suite runs, and it makes the test target's scope harder to reason about over time.

Prompt To Fix With AI
This is a comment left during a code review.
Path: Tests/FluidDictationIntegrationTests/WindowRevealTests.swift
Line: 1-5

Comment:
**Test lives in the wrong target.** `WindowRevealTests` is a focused unit test for window collection-behavior; it belongs in a general unit-test target, not `FluidDictationIntegrationTests`, which is set up for dictation end-to-end tests. Placing it here means the window regression guard only runs when the heavier integration suite runs, and it makes the test target's scope harder to reason about over time.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FluidDictationIntegrationTests is currently the repo's only test target — focused unit tests already live there (e.g. HotkeyShortcutTests, LLMClientRequestBodyTests). Happy to move this when a dedicated unit-test target exists, but adding one is out of scope for this fix.

Addresses review: permanently inserting the flag would make every future
direct ordering call also collect the window to the active Space. The
behavior is now restored on the next runloop pass; the regression test
asserts both the reveal-time flag and the restoration.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Main window can be stranded on a dead Space and becomes unreachable in menu-bar-only mode

1 participant