From 738305db731b8fa9ac6967bce21dc7c0096ce84a Mon Sep 17 00:00:00 2001 From: Nimrod Gutman Date: Fri, 1 May 2026 09:20:40 +0300 Subject: [PATCH] fix(macos): reserve exec approval dialog layout space --- CHANGELOG.md | 1 + .../OpenClaw/ExecApprovalsSocket.swift | 7 +++-- .../ExecApprovalPromptLayoutTests.swift | 31 +++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 apps/macos/Tests/OpenClawIPCTests/ExecApprovalPromptLayoutTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e9cbc26410..6858450bb29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ Docs: https://docs.openclaw.ai - Plugins/runtime-deps: hash the OS-canonical `packageRoot` via `fs.realpathSync.native` (with `path.resolve` fallback) when computing the bundled runtime-deps stage key, so loader and channel `bundled-root` callers no longer derive divergent stage directories under `~/.openclaw/plugin-runtime-deps/openclaw--/` and bundled channels stop failing with `ENOENT` on shared dist chunks under Windows npm symlinks, junctions, or PM2 multi-instance worker layouts. Fixes #74963. (#75048) Thanks @openperf and @vincentkoc. - fix(logging): add redaction patterns for Tencent Cloud, Alibaba Cloud, HuggingFace and Replicate API keys (#58162). Thanks @gavyngong - Pairing: surface unexpected allowlist filesystem stat errors instead of treating the allowlist as missing, so permission and I/O failures are visible during pairing authorization checks. (#63324) Thanks @franciscomaestre. +- macOS app: reserve layout space for exec approval command details so the allow dialog no longer overlaps the command, context, and action buttons. Thanks @ngutman. ## 2026.4.29 diff --git a/apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift b/apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift index 291d7961965..98fa9e55d29 100644 --- a/apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift +++ b/apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift @@ -253,12 +253,11 @@ enum ExecApprovalsPromptPresenter { } @MainActor - private static func buildAccessoryView(_ request: ExecApprovalPromptRequest) -> NSView { + static func buildAccessoryView(_ request: ExecApprovalPromptRequest) -> NSView { let stack = NSStackView() stack.orientation = .vertical stack.spacing = 8 stack.alignment = .leading - stack.translatesAutoresizingMaskIntoConstraints = false stack.widthAnchor.constraint(greaterThanOrEqualToConstant: 380).isActive = true let commandTitle = NSTextField(labelWithString: "Command") @@ -337,6 +336,10 @@ enum ExecApprovalsPromptPresenter { footer.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize) stack.addArrangedSubview(footer) + // NSAlert reserves accessory space from the view frame, not from Auto Layout constraints. + // Give the top-level accessory an explicit frame so its subviews do not paint over the + // alert title, message, and buttons while the frame remains zero-sized. + stack.frame = NSRect(origin: .zero, size: stack.fittingSize) return stack } diff --git a/apps/macos/Tests/OpenClawIPCTests/ExecApprovalPromptLayoutTests.swift b/apps/macos/Tests/OpenClawIPCTests/ExecApprovalPromptLayoutTests.swift new file mode 100644 index 00000000000..1819e28525f --- /dev/null +++ b/apps/macos/Tests/OpenClawIPCTests/ExecApprovalPromptLayoutTests.swift @@ -0,0 +1,31 @@ +import AppKit +import Testing +@testable import OpenClaw + +@Suite(.serialized) +@MainActor +struct ExecApprovalPromptLayoutTests { + @Test func `accessory view reserves nonzero alert layout space`() { + let accessory = ExecApprovalsPromptPresenter.buildAccessoryView( + ExecApprovalPromptRequest( + command: "/bin/sh -lc \"hostname; uptime; echo '---'\"", + cwd: "/Users/example/projects/openclaw", + host: "node", + security: "allowlist", + ask: "on-miss", + agentId: "main", + resolvedPath: "/bin/sh", + sessionKey: "session-1")) + + #expect(accessory.frame.width >= 380) + #expect(accessory.frame.height >= 160) + + let alert = NSAlert() + alert.messageText = "Allow this command?" + alert.informativeText = "Review the command details before allowing." + alert.accessoryView = accessory + + #expect(alert.accessoryView?.frame.width == accessory.frame.width) + #expect(alert.accessoryView?.frame.height == accessory.frame.height) + } +}