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:
2026-08-07 12:55:45 -04:00
parent c686242a14
commit b0ffff1aa1
14 changed files with 579 additions and 36 deletions
+6
View File
@@ -27,8 +27,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
/// It starts nothing the board-open path waits on: the listener writes cached facts that a
/// *later* composition may read, and never reaches into a session that is already open
/// (`ProEntitlement`).
///
/// **The appearance override applies here too, for the same reason.** `AppearanceStore.init`
/// only reads; this is the one call that hands its answer to `NSApp` the global side effect
/// `KanbanApp.init` must not carry, since a unit-test host runs that `init` on every launch
/// (`AppearanceStore.applyCurrent`).
func applicationDidFinishLaunching(_ notification: Notification) {
appModel?.entitlement.start()
appModel?.appearance.applyCurrent()
}
/// **The close is respected.** "Closing the last board window leaves the app windowless (menu bar