Skip to content

Commit 8f74762

Browse files
authored
fix(macos): merge the WebChat window title band into the toolbar row (#109318)
The standalone chat window stacked a visible title bar (traffic lights + "OpenClaw" title) above the SwiftUI toolbar row, wasting a full band of vertical space. Apply the Dashboard window's merged chrome: hidden title, transparent titlebar, no separator, movable-by-background — the unified toolbar (sidebar toggle, search, pickers, session actions) now shares its row with the traffic lights. A window subclass pins titleVisibility to hidden because SwiftUI's toolbar bridge can restore visible title chrome, and scene bridging is reset to toolbars-only after controller attachment (attaching an NSHostingController resets it to .all).
1 parent b5eb897 commit 8f74762

2 files changed

Lines changed: 47 additions & 4 deletions

File tree

apps/macos/Sources/OpenClaw/WebChatSwiftUI.swift

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ private enum WebChatSwiftUILayout {
1818
static let anchorPadding: CGFloat = 8
1919
}
2020

21+
/// SwiftUI's native toolbar bridge may restore visible title chrome while it
22+
/// installs toolbar items. Keep the full-window chat's titlebar merged.
23+
private final class WebChatWindow: NSWindow {
24+
override var titleVisibility: NSWindow.TitleVisibility {
25+
didSet {
26+
if self.titleVisibility != .hidden {
27+
self.titleVisibility = .hidden
28+
}
29+
}
30+
}
31+
}
32+
2133
struct MacGatewayChatTransport: OpenClawChatTransport {
2234
/// Shared across transport value copies so the live view model and its
2335
/// snapshot observer cannot diverge on the owner of the bare global alias.
@@ -580,7 +592,6 @@ final class WebChatSwiftUIWindowController {
580592
let hosting = NSHostingController(rootView: OpenClawChatWindowShell(
581593
viewModel: vm,
582594
userAccent: accent))
583-
hosting.sceneBridgingOptions = [.toolbars, .title]
584595
self.contentController = hosting
585596
case .panel:
586597
// Anchored quick-chat panel: compact single-column chat.
@@ -705,16 +716,25 @@ final class WebChatSwiftUIWindowController {
705716
{
706717
switch presentation {
707718
case .window:
708-
let window = NSWindow(
719+
let window = WebChatWindow(
709720
contentRect: NSRect(origin: .zero, size: WebChatSwiftUILayout.windowSize),
710721
styleMask: [.titled, .closable, .resizable, .miniaturizable, .fullSizeContentView],
711722
backing: .buffered,
712723
defer: false)
713724
window.title = "OpenClaw Chat"
714725
window.contentViewController = contentViewController
726+
// Attaching an NSHostingController resets scene bridging to `.all`;
727+
// opt back into toolbar items only so SwiftUI cannot restore the title.
728+
(contentViewController as? NSHostingController<OpenClawChatWindowShell>)?
729+
.sceneBridgingOptions = [.toolbars]
715730
window.isReleasedWhenClosed = false
716-
window.titleVisibility = .visible
731+
// Keep the SwiftUI toolbar controls, but merge their unified row
732+
// with the traffic lights instead of stacking it below a title band.
733+
window.titleVisibility = .hidden
734+
window.titlebarAppearsTransparent = true
717735
window.toolbarStyle = .unified
736+
window.titlebarSeparatorStyle = .none
737+
window.isMovableByWindowBackground = true
718738
window.center()
719739
window.setFrameAutosaveName(WebChatSwiftUILayout.windowFrameAutosaveName)
720740
WindowPlacement.ensureOnScreen(window: window, defaultSize: WebChatSwiftUILayout.windowSize)
@@ -801,4 +821,14 @@ final class WebChatSwiftUIWindowController {
801821
private static func color(fromHex raw: String?) -> Color? {
802822
ColorHexSupport.color(fromHex: raw)
803823
}
824+
825+
#if DEBUG
826+
var _testWindow: NSWindow? {
827+
self.window
828+
}
829+
830+
var _testSceneBridgingOptions: NSHostingSceneBridgingOptions? {
831+
(self.contentController as? NSHostingController<OpenClawChatWindowShell>)?.sceneBridgingOptions
832+
}
833+
#endif
804834
}

apps/macos/Tests/OpenClawIPCTests/WebChatSwiftUISmokeTests.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,25 @@ struct WebChatSwiftUISmokeTests {
4141
func setActiveSessionKey(_: String) async throws {}
4242
}
4343

44-
@Test func `window controller show and close`() {
44+
@Test func `window controller merges titlebar and keeps toolbar controls`() throws {
4545
let controller = WebChatSwiftUIWindowController(
4646
sessionKey: "main",
4747
presentation: .window,
4848
transport: TestTransport())
49+
let window = try #require(controller._testWindow)
50+
51+
#expect(window.styleMask.contains(.fullSizeContentView))
52+
#expect(window.titleVisibility == .hidden)
53+
#expect(window.titlebarAppearsTransparent)
54+
#expect(window.toolbarStyle == .unified)
55+
#expect(window.titlebarSeparatorStyle == .none)
56+
#expect(window.isMovableByWindowBackground)
57+
#expect(controller._testSceneBridgingOptions?.contains(.toolbars) == true)
58+
#expect(controller._testSceneBridgingOptions?.contains(.title) == false)
59+
4960
controller.show()
61+
#expect(window.titleVisibility == .hidden)
62+
#expect(window.toolbar != nil)
5063
controller.close()
5164
}
5265

0 commit comments

Comments
 (0)