The board wears a picture — background becomes a mapping, and the window chrome follows it under a thin frost

background is {color:, image:} and only a mapping at every level; the board's image paints the full window under a transparent title bar, with a thin-material frost strip keeping the chrome legible and the standard accommodations intact.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-07 10:15:03 -04:00
parent 190a8e36f1
commit d5ad21c3da
37 changed files with 1182 additions and 76 deletions
+15
View File
@@ -203,6 +203,21 @@ struct BoardWindowHost: View {
.onChange(of: boardSearch.isFocused) { _, _ in
boardSearch.dismissTransientIfCleared(query: store.searchQuery)
}
// **The window chrome follows the board's background** (03-board-ui.md § Styling
// Capabilities): a board that paints one runs its content the full height of the frame
// under a transparent title bar, with `BoardView.boardBackground`'s frosted strip
// keeping the widget and the toolbar legible over it; a board that paints none keeps
// the standard chrome untouched.
//
// Here rather than in `configureWindow` because it is not a wiring fact but a *live*
// one: `background` is hand-editable, the watcher reloads on a change to `index.md`, and
// the chrome has to follow the reading in both directions. `initial: true` because the
// first render is already a level, not a change this is the board's first statement
// about its chrome, and the loading half deliberately made none
// (`HostedWindowController.extendsUnderTitlebar`).
.onChange(of: BoardBackdrop.isCustom(store.snapshot, root: store.rootURL), initial: true) { _, custom in
windowController.setExtendsContentUnderTitlebar(custom)
}
// **The board settings sheet** (03-board-ui.md Board settings sheet) presented from
// the board window's own content, which is what makes it modal to *this* board rather
// than to the app: "a board-scoped, titled, sectioned sheet on the board window".
+52
View File
@@ -109,6 +109,24 @@ final class HostedWindowController: NSObject, NSWindowDelegate {
/// what the chrome draws from it.
private var titleVisibility: NSWindow.TitleVisibility?
/// Whether this window's content runs the full height of the frame, under a transparent title
/// bar **a board window carrying a custom background**, and nothing else (03-board-ui.md §
/// Styling Capabilities: the board's colour or image "paints the full window"; `BoardView
/// .boardBackground` draws the frosted strip that keeps the chrome legible over it).
///
/// `nil` leaves AppKit's own posture untouched, exactly as `titleVisibility` does the welcome,
/// bootstrap and card windows have no opinion, and neither does a board window while it loads
/// (the flag is driven off the snapshot, which does not exist yet). `nil` and `false` therefore
/// render identically; they differ only in whether this controller has *said* anything, which is
/// what keeps the loading half from having to state a default it does not own.
///
/// A slot rather than a one-shot write, and **repeat-safe rather than install-once** the
/// `hideTitle` pattern, for a stronger version of its reason: the value has to survive the
/// provisional-window swap (`detach()`), *and* it genuinely changes over a window's life. A
/// `background:` edited on disk reloads the snapshot, and the chrome follows it in both
/// directions.
private var extendsUnderTitlebar: Bool?
private static let logger = Logger(subsystem: "dev.rzen.indie.Kanban", category: "window")
// MARK: Attachment
@@ -129,6 +147,7 @@ final class HostedWindowController: NSObject, NSWindowDelegate {
addTitlebarAccessoryIfPossible()
applyToolbarIfPossible()
applyTitleVisibilityIfPossible()
applyTitlebarExtensionIfPossible()
}
/// Puts the previous delegate back and takes the titlebar accessory and toolbar off the window
@@ -233,6 +252,39 @@ final class HostedWindowController: NSObject, NSWindowDelegate {
window.titleVisibility = titleVisibility
}
// MARK: Content under the title bar
/// Runs this window's content the full height of its frame, under a transparent title bar or
/// puts the standard chrome back (see `extendsUnderTitlebar`).
///
/// Safe whenever the caller learns the answer before the window exists (held, applied at
/// `attach`) or after (applied now) and safe to call repeatedly with the same value, which
/// matters more here than for `hideTitle`: the board window drives this off its snapshot, so it
/// is called on every reload that changes the reading and on plenty that do not.
///
/// **Not undone at `detach`**, `titleVisibility`'s posture: the slot survives the provisional-
/// window swap and reapplies itself to whichever window attaches next, and a window that is
/// genuinely going away takes its chrome with it.
func setExtendsContentUnderTitlebar(_ flag: Bool) {
extendsUnderTitlebar = flag
applyTitlebarExtensionIfPossible()
}
/// The two AppKit knobs the effect needs, and they are one decision: `fullSizeContentView` is
/// what lets the content view reach under the title bar, and `titlebarAppearsTransparent` is
/// what stops the title bar from painting its own material over it. Either alone is a visible
/// half-state an opaque bar over the board, or a board that stops at a bar that no longer
/// draws.
private func applyTitlebarExtensionIfPossible() {
guard let window, let extendsUnderTitlebar else { return }
window.titlebarAppearsTransparent = extendsUnderTitlebar
if extendsUnderTitlebar {
window.styleMask.insert(.fullSizeContentView)
} else {
window.styleMask.remove(.fullSizeContentView)
}
}
/// Closes the window for real, after the flush has run. `performClose` rather than `close` so the
/// standard path runs SwiftUI's own delegate gets its callbacks, tabbing behaves with the
/// flag telling our own `windowShouldClose` to stand aside.