The app learns Appearance — Auto, Light, Dark from the View menu and a toolbar pull-down
View ▸ Appearance (11-command-nexus.md): three radio-exclusive rows, app-wide, persisted, needing no window in front — the View menu's new last group. AppearanceStore owns the override's rules (absent key = Auto, lenient reads degrade to Auto, remove-at-default) with an injectable apply seam so test hosts never touch NSApp; the one real apply hands NSApp.appearance its answer in applicationDidFinishLaunching, the global side effect KanbanApp.init must not carry. The board toolbar gains its first .picker item — an NSMenuToolbarItem whose rows re-fetch their spec fresh, checkmark read at menu-open like every other menu row — and Appearance joins the search field as the second default item, centered beside it (03-board-ui.md ▸ Toolbar, ratified 2026-08-07). Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - View ▸ Appearance
|
||||
|
||||
/// View ▸ Appearance — Auto / Light / Dark (11-command-nexus.md ▸ View; 03-board-ui.md ▸ Toolbar).
|
||||
///
|
||||
/// **App-wide and always enabled**, unlike `ZoomCommands` beside it in the View menu: appearance is a
|
||||
/// preference about how *every* window in the app draws, welcome included, so this row needs no
|
||||
/// `@FocusedValue` scoping and no board window in front — `NewBoardCommand`'s posture (everywhere, no
|
||||
/// focus required) rather than `ZoomCommands`' (board windows only).
|
||||
///
|
||||
/// A `Picker` rather than three independent toggles: SwiftUI renders one placed directly in a
|
||||
/// menu-bar command group as a submenu — "Appearance" as its title, "Auto" / "Light" / "Dark" as its
|
||||
/// rows, a checkmark on whichever is selected — which is the three-way exclusive choice a trio of
|
||||
/// `Toggle`s cannot express (nothing stops more than one, or none, from reading as checked). `nil` is
|
||||
/// the Auto tag; `AppearanceStore.setOverride` is the single write path the board-toolbar item shares
|
||||
/// (`BoardZoomStore.step`'s rule — a toolbar item is a menu command with a different face, never a
|
||||
/// second implementation of it).
|
||||
struct AppearanceCommands: View {
|
||||
|
||||
/// A plain `let` rather than an `@Environment` read, `ZoomCommands`' reason: menu commands live in
|
||||
/// the menu bar, outside every scene's environment; the row re-renders on a change because
|
||||
/// `AppModel` is `@Observable` (`NewBoardCommand`'s pattern).
|
||||
let appModel: AppModel
|
||||
|
||||
var body: some View {
|
||||
Picker("Appearance", selection: Binding(
|
||||
get: { appModel.appearance.override },
|
||||
set: { appModel.appearance.setOverride($0) }
|
||||
)) {
|
||||
Text("Auto").tag(nil as AppAppearance?)
|
||||
Text("Light").tag(AppAppearance.light as AppAppearance?)
|
||||
Text("Dark").tag(AppAppearance.dark as AppAppearance?)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user