From 4e9c11c56c28a076252a512c74f07f2074c2279c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 18 May 2026 17:47:49 +0000 Subject: [PATCH] Fix system theme not honoring macOS dark mode preferredColorScheme(nil) doesn't reliably follow system appearance in MenuBarExtra panel windows. Replace it with a reactive AppearanceModifier that reads NSApp.effectiveAppearance on init and re-reads it on AppleInterfaceThemeChangedNotification, so system theme stays in sync. Also remove a hardcoded .preferredColorScheme(.light) inside SpeakerNamingView that overrode the window-level setting. https://claude.ai/code/session_01VdiLFzkxe4Ra12ZXwcfWdt --- Sources/Heard/MTApp.swift | 6 +++--- Sources/HeardCore/Views.swift | 36 ++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/Sources/Heard/MTApp.swift b/Sources/Heard/MTApp.swift index 6ab4172..a18c628 100644 --- a/Sources/Heard/MTApp.swift +++ b/Sources/Heard/MTApp.swift @@ -89,7 +89,7 @@ struct HeardApp: App { var body: some Scene { MenuBarExtra { MenuBarView(model: appModel) - .preferredColorScheme(appModel.settingsStore.settings.appearance.colorScheme) + .heardAppearance(appModel.settingsStore.settings.appearance) } label: { MenuBarIcon(model: appModel) } @@ -97,7 +97,7 @@ struct HeardApp: App { Window("Heard Settings", id: "settings") { SettingsView(model: appModel) - .preferredColorScheme(appModel.settingsStore.settings.appearance.colorScheme) + .heardAppearance(appModel.settingsStore.settings.appearance) .onAppear { WindowActivationCoordinator.begin("settings") } .onDisappear { WindowActivationCoordinator.end("settings") } } @@ -106,7 +106,7 @@ struct HeardApp: App { Window("Name Speakers", id: "speaker-naming") { SpeakerNamingView(model: appModel) - .preferredColorScheme(appModel.settingsStore.settings.appearance.colorScheme) + .heardAppearance(appModel.settingsStore.settings.appearance) .onAppear { WindowActivationCoordinator.begin("speaker-naming") } .onDisappear { WindowActivationCoordinator.end("speaker-naming") diff --git a/Sources/HeardCore/Views.swift b/Sources/HeardCore/Views.swift index 91d540b..5c942bf 100644 --- a/Sources/HeardCore/Views.swift +++ b/Sources/HeardCore/Views.swift @@ -37,6 +37,41 @@ public extension AppAppearance { } } +// MARK: - Appearance Modifier + +private struct AppearanceModifier: ViewModifier { + let appearance: AppAppearance + @State private var systemIsDark = NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + + func body(content: Content) -> some View { + content + .preferredColorScheme(resolvedScheme) + .onReceive( + DistributedNotificationCenter.default() + .publisher(for: NSNotification.Name("AppleInterfaceThemeChangedNotification")) + ) { _ in + // Short delay: effectiveAppearance may not yet reflect the change + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + systemIsDark = NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + } + } + } + + private var resolvedScheme: ColorScheme? { + switch appearance { + case .system: systemIsDark ? .dark : .light + case .light: .light + case .dark: .dark + } + } +} + +public extension View { + func heardAppearance(_ appearance: AppAppearance) -> some View { + modifier(AppearanceModifier(appearance: appearance)) + } +} + // MARK: - Theme enum HeardTheme { @@ -2150,7 +2185,6 @@ public struct SpeakerNamingView: View { } .frame(width: 560) .background(HeardTheme.Paper.bg) - .preferredColorScheme(.light) .onAppear { startCountdown() } .onDisappear { stopAudio()