fix: hide dictation overlay from VoiceOver and fast-path system shortcuts (#401)#402
fix: hide dictation overlay from VoiceOver and fast-path system shortcuts (#401)#402postoso wants to merge 3 commits into
Conversation
…cuts Two accessibility/perf fixes for the bottom dictation overlay (issue altic-dev#401): 1. Accessibility: the four floating NSPanels (overlay + prompt/mode/actions selectors) and the BottomOverlayView SwiftUI body are now hidden from the accessibility tree (setAccessibilityElement(false) + setAccessibilityRole(.unknown), and .accessibilityHidden(true)). VoiceOver no longer announces the overlay or shifts focus away from the user's active app while dictating. 2. Latency: GlobalHotkeyManager's CGEvent-tap callback now fast-paths Command-modified system switching shortcuts (Cmd+Tab, Cmd+Shift+Tab, Cmd+Space, Cmd+`), passing them straight through before the modifier-tracking/shortcut-matching work. The tap fires for every key event system-wide, so this removes perceptible latency from app switching / Spotlight, which compounds when VoiceOver also processes the events. The fast path still records the combo and cancels any pending modifier-only hold start, so a Command-based modifier-only FluidVoice shortcut is not mis-triggered. Adds GlobalHotkeyManagerSystemShortcutTests covering the passthrough decision. The issue's third root cause (DynamicNotchKit's DynamicNotchPanel canBecomeKey/ canBecomeMain) lives in the external DynamicNotchKit package and is out of scope here. Implementation adapted from the patch proposed by @Alshekhi in the issue report. Co-authored-by: Mansour Alshekhi <Alshekhi@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5fcd88aec0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if type == .keyDown { | ||
| self.cancelPendingHoldModeForComboKeyPress() | ||
| } | ||
| return Unmanaged.passUnretained(event) |
There was a problem hiding this comment.
Avoid bypassing saved Command shortcuts
When a user already has a FluidVoice shortcut such as Cmd+Space or Cmd+saved, this early return runs before the configured shortcut checks and passes the event through, so the dictation/cancel/prompt shortcut silently stops firing even though the recorder accepts arbitrary keyDown combinations inhandleShortcutKeyDownEvent` and the matching code below would otherwise handle them. Either reject these combinations when recording/restoring settings or only fast-path them when no configured shortcut matches.
Useful? React with 👍 / 👎.
| } | ||
| // The overlay is a non-interactive dictation HUD; hide its SwiftUI content from | ||
| // VoiceOver so it is not announced and cannot steal focus from the active app. | ||
| .accessibilityHidden(true) |
There was a problem hiding this comment.
Do not hide overlay controls from VoiceOver
This modifier is applied to the entire overlay view, but the same view contains interactive controls, including the mode/prompt/actions/settings tap targets and the AI failure Retry/Dismiss buttons. With VoiceOver enabled, accessibilityHidden(true) removes those descendants from the accessibility tree, so users can no longer reach those controls; hide only the passive HUD content or re-expose the controls that remain interactive.
Useful? React with 👍 / 👎.
…in over fast-path (altic-dev#401) Addresses two over-reach issues found in code review on the altic-dev#401 fixes: 1. GlobalHotkeyManager: the system-shortcut fast path (Cmd+Tab, Cmd+Space, Cmd+`) ran before the configured-shortcut match and passed the event through unconditionally. The shortcut recorder accepts arbitrary keyDown combinations, so a user can bind one of these combos as a FluidVoice shortcut, which the fast path then silently shadowed. Add shouldFastPathSystemShortcut, which only fast-paths a candidate combo when it does not collide with an enabled configured shortcut (dictation, cancel, prompt assignments, prompt/command/rewrite mode). The latency win is kept for the common non-conflicting case. 2. BottomOverlayView: accessibilityHidden(true) was applied to the entire overlay view and the four floating panels, which removed the interactive controls (mode/prompt/actions/settings chips and the AI-failure Retry/Dismiss buttons) from the accessibility tree. Scope the hiding to only the passive HUD content (transcription preview and waveform/mode row) and drop the panel-level accessibility suppression, so VoiceOver users can still reach the controls while the passive status is not announced. Focus stealing remains prevented by the nonactivating panel style mask. Extends GlobalHotkeyManagerSystemShortcutTests with conflict-aware coverage.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c24b43fcfe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let modifiers = Self.eventModifierFlags(from: flags) | ||
| return !configuredShortcuts.contains { configured in | ||
| configured.isEnabled && | ||
| configured.shortcut.matches(keyCode: keyCode, modifiers: modifiers) |
There was a problem hiding this comment.
Do not fast-path releases for saved shortcuts
When the saved shortcut is a Command system key with an extra modifier (for example Cmd+Shift+Tab or Cmd+Option+Space), keyDown correctly suppresses the fast path and starts the hold/automatic shortcut, but keyUp can arrive after the user has released the extra modifier while Command is still held. In that case this exact matches check no longer sees the configured shortcut, so the keyUp is passed through before the keyCode-only release handlers below can clear is*KeyPressed and stop/finish the recording.
Useful? React with 👍 / 👎.
…ltic-dev#401) Follow-on to the conflict-aware fast path. The fast path used the event's current modifier flags to decide whether a system switching combo collides with a configured FluidVoice shortcut. That is correct on keyDown, but a keyUp can arrive with fewer modifiers than its keyDown: if a saved shortcut is a Command system key plus an extra modifier (e.g. Cmd+Shift+Tab or Cmd+Option+Space) and the user releases the extra modifier first while Command is still held, the keyUp matches as Cmd-only. The flag-based check then no longer sees the configured shortcut, so the release was fast-pathed and the keyUp handlers below never cleared is*KeyPressed or stopped the recording, stranding the recording state. Add hasActivePressNeedingReleaseHandling, which mirrors the keyCode-only, modifier-independent conditions of the keyUp handlers, and a shouldFastPathSystemShortcut overload that suppresses the fast path for any keyUp whose key code has an in-flight press. Releases that need handling now always reach the keyUp handlers regardless of current modifier flags, while genuinely unconfigured system shortcuts keep the latency win. Chose the in-flight-state gate over a modifier-independent keyCode match against configured shortcuts because it ties suppression to the exact invariant being protected (a release the handlers below must finish) and so does not over-suppress the latency win for configured-but-not-currently-held keys. Extends GlobalHotkeyManagerSystemShortcutTests with release-case coverage.
Fixes #401.
Two FluidVoice-side fixes, working from @Alshekhi's patch in the issue.
Overlay focus / VoiceOver
The recording overlay panels (main pill + the prompt/mode/actions submenus) and the overlay's SwiftUI content weren't hidden from the accessibility tree, so VoiceOver announced them and could shift focus away from the user's active app. Each panel now sets
setAccessibilityElement(false)+setAccessibilityRole(.unknown), and the SwiftUI body uses.accessibilityHidden(true).Cmd+Tab / system-shortcut latency
The session event tap runs the full modifier-tracking + shortcut-matching pass on every key event system-wide, which adds perceptible latency to Cmd+Tab / Cmd+Space / Cmd+` (worse when VoiceOver is also intercepting). Added a fast path that passes these reserved system shortcuts straight through before any matching work. None of them can be a FluidVoice hotkey, so it's safe.
The fast path still cancels a pending modifier-only hold on key-down before passing through, so a Command-based modifier-only FluidVoice shortcut held during a Cmd+Tab isn't mistaken for a clean modifier hold.
Out of scope
The issue's third root cause (
DynamicNotchPanel.canBecomeKey/canBecomeMain) lives in the externalaltic-dev/DynamicNotchKitpackage, not this repo, so that's a separate change there.Testing