The clear title bar is said in SwiftUI — a configuration pass stops painting the bar back over the board's picture

`titlebarAppearsTransparent` is not the app's to hold on a scene window: it is
the AppKit face of the tree's resolved `toolbarBackgroundVisibility`, and
SwiftUI writes it on every pass that re-applies a window's configuration. The
board stated nothing, so `.automatic` resolved and each pass put `false` back
over what `HostedWindowController` had set out of band — correct on open,
because the walk lands after the pass that follows window creation, and opaque
at the next one, with nothing to re-assert it since the board's own reading had
not changed. The board now declares the posture where SwiftUI will keep
asserting it; the AppKit write stays as the same value one turn earlier.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-09 09:44:30 -04:00
parent ece33bbf78
commit a429a7ec4d
3 changed files with 146 additions and 1 deletions
+89
View File
@@ -1,4 +1,6 @@
import AppKit
import Foundation
import SwiftUI
import Testing
@testable import Kanban
@@ -441,3 +443,90 @@ struct BackgroundImagePathTests {
#expect(BoardBackdrop.isCustom(try board("background: {color: mauve, image: /tmp/x.jpg}"), root: root) == false)
}
}
// MARK: - The window chrome that follows the background
/// **A board that paints a background runs its content under a clear title bar** (03-board-ui.md §
/// Styling Capabilities), and that posture is said in two places on purpose.
///
/// `NSWindow.titlebarAppearsTransparent` belongs to SwiftUI on a scene window it is the AppKit
/// face of `toolbarBackgroundVisibility(for: .windowToolbar)`, and SwiftUI writes the value its tree
/// resolves on every pass in which it re-applies a window's configuration. A tree that said nothing
/// resolved `.automatic`, so those passes wrote `false` back over the flag `HostedWindowController`
/// had set out of band: the board opened correct and its title bar turned opaque some time later,
/// painting its own material and its separator hairline across a picture still being drawn full
/// height underneath. So `BoardWindowHost` states it in SwiftUI as well, and the two tests here pin
/// the two halves the rule SwiftUI is told, and the window the controller writes.
@MainActor
@Suite("Board background ▸ the window chrome follows it")
struct BoardChromeTests {
@Test("The toolbar background is gone over a board that paints one, and the system's otherwise")
func toolbarBackgroundFollowsTheBoard() {
#expect(BoardWindowHost.toolbarBackground(overCustomBackground: true) == .hidden)
// `.automatic`, never `.visible` a board with no background of its own keeps whatever the
// system would have drawn, which is what "the standard chrome, untouched" means.
#expect(BoardWindowHost.toolbarBackground(overCustomBackground: false) == .automatic)
}
@Test("The controller says nothing about a window's title bar until it is told")
func silentUntilTold() {
let window = Self.window()
let controller = HostedWindowController()
controller.attach(to: window)
// `nil` and `false` render identically; they differ only in whether this controller has
// spoken, which is what keeps the loading half from stating a default it does not own.
#expect(window.titlebarAppearsTransparent == false)
#expect(window.styleMask.contains(.fullSizeContentView) == false)
}
@Test("Both knobs move together, in both directions, however often they are set")
func bothKnobsMoveTogether() {
let window = Self.window()
let controller = HostedWindowController()
controller.attach(to: window)
controller.setExtendsContentUnderTitlebar(true)
#expect(window.titlebarAppearsTransparent)
#expect(window.styleMask.contains(.fullSizeContentView))
// Repeat-safe: the board drives this off its snapshot, so it is called on every reload that
// changes the reading and on plenty that do not.
controller.setExtendsContentUnderTitlebar(true)
#expect(window.titlebarAppearsTransparent)
#expect(window.styleMask.contains(.fullSizeContentView))
// And back a `background:` deleted on disk puts the standard chrome back.
controller.setExtendsContentUnderTitlebar(false)
#expect(window.titlebarAppearsTransparent == false)
#expect(window.styleMask.contains(.fullSizeContentView) == false)
}
@Test("The posture survives the provisional-window swap")
func theSlotReappliesToTheNextWindow() {
// SwiftUI dismantles and re-makes the background representable while it moves a scene's
// content into its final window, so a posture stated against the first window has to reach
// the second (`HostedWindowController.detach`).
let provisional = Self.window()
let controller = HostedWindowController()
controller.attach(to: provisional)
controller.setExtendsContentUnderTitlebar(true)
controller.detach()
let real = Self.window()
controller.attach(to: real)
#expect(real.titlebarAppearsTransparent)
#expect(real.styleMask.contains(.fullSizeContentView))
}
private static func window() -> NSWindow {
NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 600, height: 400),
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered,
defer: true
)
}
}