Bring tooltips back on macOS 26 beta + show accept key as a keycap#350
Conversation
Two changes on the same Settings/menu-bar surface: 1. SwiftUI's .help(_:) silently stopped rendering tooltips for LSUIElement apps on the macOS 26 beta, so every tooltip we added in #313 was invisible. Wrap a click-through NSViewRepresentable that sets NSView.toolTip directly (AppKit's tooltip subsystem still works there) and expose it as .cotabbyHelp(_:). Replace all 44 .help( call sites in SettingsView, MenuBarView, CustomRulesEditor, and LanguageTagsEditor. Still calls .help(_:) underneath so accessibility help stays wired up and the SwiftUI path 'just starts working' again on a future macOS update. 2. Rename 'Show Accept Hint' to a live label that renders the user's actual accept key as an inline keycap pill — 'Show [Tab] Key Hint', 'Show [⌥Tab] Key Hint', etc. The static name was vague about what the hint actually shows.
| Toggle(isOn: showAcceptanceHintBinding) { | ||
| HStack(spacing: 4) { | ||
| Text("Show") | ||
| Text(suggestionSettings.acceptanceKeyLabel) | ||
| .font(.system(.body, design: .rounded).weight(.semibold)) | ||
| .padding(.horizontal, 5) | ||
| .padding(.vertical, 1) | ||
| .background( | ||
| RoundedRectangle(cornerRadius: 4, style: .continuous) | ||
| .fill(.quaternary) | ||
| ) | ||
| Text("Key Hint") | ||
| } | ||
| } | ||
| .cotabbyHelp("Show a small label near the ghost text reminding you which key accepts it.") |
There was a problem hiding this comment.
Keycap pill shows "None" when acceptance key is disabled
When the user clears the word-by-word acceptance key, acceptanceKeyLabel becomes "None" (the disabledKeyLabel constant), so the toggle label renders as "Show [None] Key Hint". This is accurate but may read oddly — a user who has unbound the key sees a hint referring to a non-existent binding. Consider falling back to a plain "Show Key Hint" label (no pill) when acceptanceKeyCode == SuggestionSettingsModel.disabledKeyCode.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| func body(content: Content) -> some View { | ||
| content | ||
| .help(text) | ||
| .overlay(TooltipOverlayView(text: text)) | ||
| } |
There was a problem hiding this comment.
Duplicate tooltip risk on future macOS where SwiftUI's bridge is repaired
Both .help(text) and the NSView.toolTip tracking area will be active simultaneously. On a macOS version that fixes the bridge, two separate tooltip managers may each fire for the same hover, producing a double tooltip. The PR notes this will "become a harmless no-op", but that assumes only one system fires — which isn't guaranteed. Adding a version gate (e.g., if #unavailable(macOS X.Y)) around the overlay would prevent the double-fire once a fixed OS ships and makes the intended cleanup path explicit.
| func body(content: Content) -> some View { | |
| content | |
| .help(text) | |
| .overlay(TooltipOverlayView(text: text)) | |
| } | |
| func body(content: Content) -> some View { | |
| // On macOS 26 beta SwiftUI's tooltip bridge is broken for LSUIElement apps (issue #313). | |
| // Overlay an AppKit NSView so the tooltip is still shown via AppKit's own subsystem. | |
| // When SwiftUI's bridge is fixed, gate the overlay out with `if #unavailable(macOS X.Y)` | |
| // to avoid both systems firing simultaneously. | |
| content | |
| .help(text) | |
| .overlay(TooltipOverlayView(text: text)) | |
| } |
Summary
Two fixes to the Settings + menu-bar surface, both reported in one ticket:
The tooltip PR (Add tooltips across Settings and the menu-bar quick controls #313) sprinkled 40+ `.help(:)` modifiers across Settings, the menu-bar panel, and the rule/language editors — but on macOS 26 beta SwiftUI's tooltip bridge silently stopped firing for LSUIElement (menu-bar) apps, so none of those tooltips were ever visible. AppKit's own tooltip subsystem still works fine, so this PR adds a thin `.cotabbyHelp(:)` modifier that calls `.help(:)` (for accessibility) and overlays a click-through `NSViewRepresentable` that sets `NSView.toolTip` directly. All 44 `.help(` call sites were updated. When SwiftUI's bridge is fixed in a later macOS update, the underlying `.help(:)` will already be wired up and the overlay becomes a harmless no-op.
The 'Show Accept Hint' toggle's label was vague — it didn't say what the hint shows. Replaced with a live label that renders the user's actual accept key as an inline keycap pill: 'Show [Tab] Key Hint' (or '[⌥Tab]', '[`]', etc., depending on what the user bound). Picks up rebinds automatically because it reads `suggestionSettings.acceptanceKeyLabel` directly.
Validation
UI verification recommended: open Settings on macOS 26 beta and hover any control with a tooltip — pill should appear after ~1.5 s. Repeat in the menu-bar panel. Rebind the accept key from Settings and confirm the new key glyph appears in the 'Show ... Key Hint' label without restarting.
Linked issues
Refs #313.
Risk / rollout notes
Greptile Summary
This PR fixes two issues on macOS 26 beta: it restores tooltips in the Settings window and menu-bar panel (where SwiftUI's tooltip bridge silently stopped working for LSUIElement apps) by introducing a thin
cotabbyHelp(_:)modifier that layers an AppKitNSView.toolTipoverlay over every existing.help()call, and it replaces the static "Show Accept Hint" toggle label with a live keycap pill that displays the user's currently bound acceptance key.TooltipSupport.swift(new): DefinesCotabbyTooltipModifier, which chains.help()for accessibility and overlays aClickThroughTooltipView(hitTest → nil) whosetoolTipproperty drives AppKit'sNSToolTipManagerdirectly. All 44 existing call sites are updated to.cotabbyHelp().SettingsView.swift: The "Show Accept Hint" toggle label is rebuilt as anHStackwith an inline rounded-rectangle keycap pill readingsuggestionSettings.acceptanceKeyLabel; rebinds are reflected immediately without restart.Confidence Score: 4/5
Safe to merge; the AppKit overlay is click-through by design and the tooltip workaround is well-scoped to the UI layer.
The
ClickThroughTooltipView+toolTipapproach is sound — AppKit tooltip tracking is independent of hit testing, sohitTest → nilcorrectly lets clicks pass through while still triggering tooltip popovers. The only non-trivial concerns are both non-blocking: when the acceptance key is cleared, the keycap pill reads "None" (producing "Show [None] Key Hint"), and once a future macOS ships with a fixed SwiftUI tooltip bridge, both.help()and the AppKit tracking area will be active simultaneously, risking duplicate tooltips until the workaround is removed.Cotabby/UI/TooltipSupport.swift warrants a second look for the dual-tooltip scenario on future macOS; Cotabby/UI/SettingsView.swift's keycap pill is worth smoke-testing with the key unbound.
Important Files Changed
cotabbyHelp(_:)— a thin AppKit-backed tooltip workaround for macOS 26 beta. Implementation is clean; minor concern that both SwiftUI and AppKit tooltip systems will fire simultaneously on a future OS where the bridge is fixed..help()calls replaced with.cotabbyHelp(); "Show Accept Hint" toggle updated to show a live keycap pill. When the acceptance key is cleared, the pill displays "None", producing a slightly odd "Show [None] Key Hint" label..help()→.cotabbyHelp()replacements throughout; no logic changes..help()→.cotabbyHelp()replacements; no other changes..help()→.cotabbyHelp()replacements; no other changes.TooltipSupport.swiftto the Xcode project sources; standard xcodegen output.Sequence Diagram
sequenceDiagram participant SW as SwiftUI View participant CM as CotabbyTooltipModifier participant OV as TooltipOverlayView (NSViewRepresentable) participant AK as ClickThroughTooltipView (NSView) participant AM as AppKit NSToolTipManager participant ACC as Accessibility (.help) SW->>CM: .cotabbyHelp("text") CM->>ACC: .help("text") — wires up VoiceOver CM->>OV: .overlay(TooltipOverlayView) OV->>AK: "makeNSView → toolTip = "text"" AK->>AM: registers tooltip tracking area Note over AK: hitTest → nil (click-through) AM-->>SW: tooltip popover on hover (AppKit path) Note over SW,ACC: On future macOS where SwiftUI bridge is fixed,<br/>both .help() and AppKit tracking area are activeReviews (1): Last reviewed commit: "Bring tooltips back on macOS 26 beta + s..." | Re-trigger Greptile