From c50c3699d9c7b4b304a7e17d67fd9e29190eaa1b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 12 Dec 2025 22:09:14 +0000 Subject: [PATCH] fix(macos): keep voice wake overlay on top --- apps/macos/Sources/Clawdis/CanvasWindow.swift | 1 + apps/macos/Sources/Clawdis/VoiceWakeOverlay.swift | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/macos/Sources/Clawdis/CanvasWindow.swift b/apps/macos/Sources/Clawdis/CanvasWindow.swift index 05630aa5c..ba7bb8063 100644 --- a/apps/macos/Sources/Clawdis/CanvasWindow.swift +++ b/apps/macos/Sources/Clawdis/CanvasWindow.swift @@ -248,6 +248,7 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS window.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) window.makeFirstResponder(self.webView) + VoiceWakeOverlayController.shared.bringToFrontIfVisible() self.onVisibilityChanged?(true) } diff --git a/apps/macos/Sources/Clawdis/VoiceWakeOverlay.swift b/apps/macos/Sources/Clawdis/VoiceWakeOverlay.swift index e8fe20b98..5f9cd1a13 100644 --- a/apps/macos/Sources/Clawdis/VoiceWakeOverlay.swift +++ b/apps/macos/Sources/Clawdis/VoiceWakeOverlay.swift @@ -10,6 +10,10 @@ final class VoiceWakeOverlayController: ObservableObject { private let logger = Logger(subsystem: "com.steipete.clawdis", category: "voicewake.overlay") + /// Keep the voice wake overlay above any other Clawdis windows, but below the system’s pop-up menus. + /// (Menu bar menus typically live at `.popUpMenu`.) + private static let preferredWindowLevel = NSWindow.Level(rawValue: NSWindow.Level.popUpMenu.rawValue - 4) + enum Source: String { case wakeWord, pushToTalk } @Published private(set) var model = Model() @@ -308,9 +312,7 @@ final class VoiceWakeOverlayController: ObservableObject { panel.isOpaque = false panel.backgroundColor = .clear panel.hasShadow = false - // Voice wake must stay above any other Clawdis windows (Canvas, WebChat, notifications). - // `.statusBar` is shared with some other overlays, so bump it slightly to avoid ordering fights. - panel.level = NSWindow.Level(rawValue: NSWindow.Level.statusBar.rawValue + 2) + panel.level = Self.preferredWindowLevel panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .transient] panel.hidesOnDeactivate = false panel.isMovable = false @@ -326,6 +328,13 @@ final class VoiceWakeOverlayController: ObservableObject { self.window = panel } + /// Reassert window ordering when other panels are shown. + func bringToFrontIfVisible() { + guard self.model.isVisible, let window = self.window else { return } + window.level = Self.preferredWindowLevel + window.orderFrontRegardless() + } + private func targetFrame() -> NSRect { guard let screen = NSScreen.main else { return .zero } let height = self.measuredHeight()