Files
focus-fixer/docs/manual-tests.md
T
sttlab-tech 561a547b70 Initial FocusFixer implementation
Menu-bar utility that fixes cross-display keyboard focus loss: a
listen-only CGEvent tap locates the window under the cursor and
reasserts app activation + AX focus on it, with debounce, tap
auto-recovery, and an Accessibility permission gate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 12:02:29 +02:00

3.6 KiB

Manual test checklist — focus behaviour

The event tap and Accessibility side-effects can't be unit tested. Run this checklist by hand after any change to EventTapController, WindowLocator, or FocusEnforcer, and whenever behaviour changes update it alongside the code.

Prerequisites: FocusFixer built and running as a .app bundle (not via swift run), Accessibility permission granted in System Settings → Privacy & Security → Accessibility.

Basic cross-display focus

  1. Open a text editor window on display A and a terminal window on display B.
  2. Click into the terminal on display B, then click the text editor on display A.
  3. Type immediately after the click. Confirm keystrokes land in the text editor, not the terminal.
  4. Repeat in the opposite direction (A → B).
  1. Connect a DisplayLink virtual display (or enable one if already paired).
  2. Repeat the "Basic cross-display focus" steps with one of the two windows on the virtual display.
  3. Confirm focus follows clicks with no perceptible lag and no dropped keystrokes.

No focus thrashing on the active app

  1. Click repeatedly inside the already-frontmost window (same app, same display).
  2. Confirm no visible activation flicker and no window re-raise animation — NSRunningApplication.isActive should short-circuit these clicks.

Debounce

  1. Rapidly double-click and drag-select across two different windows.
  2. Confirm only one focus reassertion happens per interaction, not one per raw mouse-down.

Excluded apps

  1. Add an app's bundle ID to the excluded list (excludedBundleIDs in UserDefaults for com.local.focusfixer).
  2. Click into that app's window from another display.
  3. Confirm FocusFixer does not activate or raise it.

System UI is left alone

  1. Click the Dock, a Spotlight search result, and Control Center.
  2. Confirm none of these trigger FocusFixer activation/raise logic (check logs — see below — for absence of unexpected activation entries).

Self-activation guard

  1. Click the FocusFixer menu-bar icon.
  2. Confirm FocusFixer never tries to activate/raise itself (it has no normal windows, but verify no AX errors logged for its own PID).

Tap recovery

  1. Trigger sustained system load (e.g. a busy yes > /dev/null loop or a heavy build) to risk kCGEventTapDisabledByTimeout.
  2. Continue clicking across displays during the load.
  3. Confirm focus reassertion keeps working — tail the logs (below) for a "re-enabling" message, which indicates recovery kicked in.

Permission revoked mid-session

  1. While FocusFixer is running, run: tccutil reset Accessibility com.local.focusfixer
  2. Click across displays; confirm FocusFixer degrades silently (no dialog, no crash) and logs the AX failures.
  3. Re-grant Accessibility permission in System Settings and confirm FocusFixer resumes without a relaunch (permission polling should pick it back up if the app is still waiting; otherwise relaunch and confirm the prompt reappears correctly).

Toggling from the menu

  1. Use the menu-bar "Enabled" checkbox to disable FocusFixer.
  2. Confirm clicks across displays no longer reassert focus.
  3. Re-enable and confirm behaviour resumes.
  4. Toggle "Launch at Login", quit and relog (or use sfltool / System Settings → General → Login Items) to confirm the registration took effect.

Logs

log stream --predicate 'subsystem == "com.local.focusfixer"' --level debug

Watch for repeated tapDisabledByTimeout/tapDisabledByUserInput messages (indicates the tap callback is doing too much work) and AX error codes logged by FocusEnforcer.