Compare commits

...

2 Commits

Author SHA1 Message Date
Peter Steinberger
9e4a7b5496 fix: land mac overlay exclusivity fix (#39321) (thanks @fellanH) 2026-03-08 13:47:12 +00:00
Felix Hellström
d1b24bcbe4 macOS: fix VoiceWakeOverlayController exclusivity violation #39275 2026-03-08 13:43:46 +00:00
6 changed files with 21 additions and 6 deletions

View File

@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
- Mattermost replies: keep `root_id` pinned to the existing thread root when an agent replies inside a thread, while still using reply-target threading for top-level posts. (#27744) thanks @hnykda.
- Agents/failover: detect Amazon Bedrock `Too many tokens per day` quota errors as rate limits across fallback, cron retry, and memory embeddings while keeping context-window `too many tokens per request` errors out of the rate-limit lane. (#39377) Thanks @gambletan.
- Android/Play distribution: remove self-update, background location, `screen.record`, and background mic capture from the Android app, narrow the foreground service to `dataSync` only, and clean up the legacy `location.enabledMode=always` preference migration. (#39660) Thanks @obviyus.
- macOS overlays: fix VoiceWake, Talk, and Notify overlay exclusivity crashes by removing shared `inout` visibility mutation from `OverlayPanelFactory.present`, and add a repeated Talk overlay smoke test. (#39275, #39321) Thanks @fellanH.
## 2026.3.7

View File

@ -61,9 +61,11 @@ final class NotifyOverlayController {
self.ensureWindow()
self.hostingView?.rootView = NotifyOverlayView(controller: self)
let target = self.targetFrame()
let isFirst = !self.model.isVisible
if isFirst { self.model.isVisible = true }
OverlayPanelFactory.present(
window: self.window,
isVisible: &self.model.isVisible,
isFirstPresent: isFirst,
target: target)
{ window in
self.updateWindowFrame(animate: true)

View File

@ -64,15 +64,14 @@ enum OverlayPanelFactory {
@MainActor
static func present(
window: NSWindow?,
isVisible: inout Bool,
isFirstPresent: Bool,
target: NSRect,
startOffsetY: CGFloat = -6,
onFirstPresent: (() -> Void)? = nil,
onAlreadyVisible: (NSWindow) -> Void)
{
guard let window else { return }
if !isVisible {
isVisible = true
if isFirstPresent {
onFirstPresent?()
let start = target.offsetBy(dx: 0, dy: startOffsetY)
self.animatePresent(window: window, from: start, to: target)

View File

@ -30,9 +30,11 @@ final class TalkOverlayController {
self.ensureWindow()
self.hostingView?.rootView = TalkOverlayView(controller: self)
let target = self.targetFrame()
let isFirst = !self.model.isVisible
if isFirst { self.model.isVisible = true }
OverlayPanelFactory.present(
window: self.window,
isVisible: &self.model.isVisible,
isFirstPresent: isFirst,
target: target)
{ window in
window.setFrame(target, display: true)

View File

@ -13,9 +13,11 @@ extension VoiceWakeOverlayController {
self.ensureWindow()
self.hostingView?.rootView = VoiceWakeOverlayView(controller: self)
let target = self.targetFrame()
let isFirst = !self.model.isVisible
if isFirst { self.model.isVisible = true }
OverlayPanelFactory.present(
window: self.window,
isVisible: &self.model.isVisible,
isFirstPresent: isFirst,
target: target,
onFirstPresent: {
self.logger.log(

View File

@ -66,6 +66,15 @@ struct LowCoverageViewSmokeTests {
try? await Task.sleep(nanoseconds: 250_000_000)
}
@Test func `talk overlay presents twice and dismisses`() async {
let controller = TalkOverlayController()
controller.present()
controller.updateLevel(0.4)
controller.present()
controller.dismiss()
try? await Task.sleep(nanoseconds: 250_000_000)
}
@Test func `visual effect view hosts in NS hosting view`() {
let hosting = NSHostingView(rootView: VisualEffectView(material: .sidebar))
_ = hosting.fittingSize