Skip to content

fix: cycle panel window-switching bugs (full-screen, junk windows, custom modifiers)#37

Open
pa wants to merge 7 commits into
gouwsxander:mainfrom
pa:fix/cycle-panel-window-bugs
Open

fix: cycle panel window-switching bugs (full-screen, junk windows, custom modifiers)#37
pa wants to merge 7 commits into
gouwsxander:mainfrom
pa:fix/cycle-panel-window-bugs

Conversation

@pa

@pa pa commented Jun 13, 2026

Copy link
Copy Markdown

Summary

A set of fixes to the cycle-panel window switcher, found while testing across full-screen apps, Chromium-based browsers, and customized modifier keys. No new features — bug fixes only.

Fixes

  • Full-screen apps showed only "Focus app." Native full-screen apps report an empty kAXWindowsAttribute (the full-screen window lives on its own Space). getWindows() now falls back to the app's focused/main window — which Accessibility still exposes — so the window appears instead of the no-window action. (Reef/Models/Application.swift)

  • Chromium browsers listed blank rows. Chromium exposes hidden helper windows (empty title + AXUnknown subrole). Windows are now filtered to standard-or-titled, dropping the junk. (Reef/Models/Application.swift, isRelevantWindow(subrole:title:))

  • Custom activate-modifier broke the panel. The flags monitor hardcoded Control to detect "released," so remapping the activate modifier (e.g. to Option) either prevented committing a selection or dismissed the panel immediately. It now commits when the user's configured activate modifiers are released. (Reef/UI/CyclePanel/CyclePanelController.swift)

  • Misleading "Focus app" when Accessibility is denied. Without permission, window enumeration always returns empty. The panel now shows an "Enable Accessibility Access…" item that opens the relevant System Settings pane. (Reef/Models/CyclePanelState.swift, Reef/UI/CyclePanel/CyclePanelController.swift)

  • Non-unique Window.id. id fell back to 0 for every window whose CGWindowID couldn't be resolved, violating Identifiable uniqueness; now uses a per-instance fallback. (Reef/Models/Window.swift)

  • Fragile app-identity check. handleActivate compared apps by display title to choose the starting window index; it now compares by bundle id via a new Application.isSameApplication(as:), which also de-duplicates the identical logic in CyclePanelController.isShowingSwitcher. (Reef/Models/Application.swift, Reef/UI/KeyboardShortcuts/ShortcutController.swift)

  • Dev experience: skip the automatic Sparkle update check in DEBUG builds (ad-hoc-signed debug builds can't validate updates and hit an "improperly signed" error). Release behavior unchanged. (Reef/Updates/SparkleConnector.swift)

Known limitation (documented in code)

kAXWindowsAttribute is Space-scoped: when the current Space is a full-screen app's Space, querying another app returns zero windows, so only that app's focused window can be shown — not its full list. Enumerating windows across Spaces would need continuous background AX observation or private Spaces APIs. Noted in Application.getWindows().

Tests

New unit tests (13): activate-modifier commit predicate, the no-windows fallback action (permission-aware), and the junk-window filter. All passing via xcodebuild test.

🤖 Generated with Claude Code

pa and others added 7 commits June 14, 2026 00:16
The flags monitor hardcoded Control to detect "released", so any user
who remapped the activate modifier (e.g. to Option) either could not
commit a selection or had the panel dismiss immediately. Capture the
configured activate modifiers when the panel opens and commit when they
are released. Logic extracted to a pure, unit-tested predicate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Window.id fell back to 0 for every window whose CGWindowID could not be
  resolved, violating Identifiable uniqueness; use a per-instance fallback.
- handleActivate compared apps by display title to decide the starting
  window index; compare by bundle id instead via a new
  Application.isSameApplication(as:) helper, which also de-duplicates the
  identical logic in CyclePanelController.isShowingSwitcher.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Without Accessibility permission, window enumeration always returns empty
and the panel showed a misleading "Focus app". Detect the missing
permission and show an "Enable Accessibility Access…" item that opens the
relevant System Settings pane. Decision logic extracted to a unit-tested
CyclePanelState.fallbackAction(isRunning:isAccessibilityTrusted:).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ad-hoc signed debug builds cannot validate a downloaded update, so the
automatic launch check always failed with an "improperly signed" error.
Skip the auto-check in DEBUG; the manual "Check for updates…" item and
all release behavior are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Native full-screen apps report an empty kAXWindowsAttribute (the
full-screen window lives on its own Space), so the panel showed only
"Focus app". Fall back to the app's focused/main window, which AX still
exposes for full-screen windows, when the windows array is empty.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chromium-based apps expose hidden helper windows (empty title +
AXUnknown subrole) via kAXWindowsAttribute, which showed up as blank rows
in the switcher. Keep a window only if it is a standard window or has a
non-empty title. Predicate is unit-tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gouwsxander

Copy link
Copy Markdown
Owner

Hi @pa,

Thanks for the PR! It's really cool to see people make use of it being open source!

Currently working on getting a version of PR #30 merged. Once that's done I'd love to get a subset of these features merged as well.

I'll keep you updated!

@pa

pa commented Jun 14, 2026

Copy link
Copy Markdown
Author

Hi @pa,

Thanks for the PR! It's really cool to see people make use of it being open source!

Currently working on getting a version of PR #30 merged. Once that's done I'd love to get a subset of these features merged as well.

I'll keep you updated!

I've used a ton of window managers like Yabai, AeroSpace, Rectangle, Raycast, and Tuna, but yours is definitely unique. I really love the idea of keeping window switching so simple and clean. Sure I'll wait no problem.

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.

2 participants