fix(webchat): refresh bubbles on theme change

This commit is contained in:
Peter Steinberger
2025-12-25 22:35:46 +01:00
parent 1e4e02ddd3
commit 1c88d9575e
3 changed files with 64 additions and 6 deletions

View File

@@ -6,7 +6,39 @@ import AppKit
import UIKit
#endif
#if os(macOS)
private extension NSAppearance {
var isDarkAqua: Bool {
self.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua
}
}
#endif
enum ClawdisChatTheme {
#if os(macOS)
static func resolvedAssistantBubbleColor(for appearance: NSAppearance) -> NSColor {
// NSColor semantic colors don't reliably resolve for arbitrary NSAppearance in SwiftPM.
// Use explicit light/dark values so the bubble updates when the system appearance flips.
appearance.isDarkAqua
? NSColor(calibratedWhite: 0.18, alpha: 0.88)
: NSColor(calibratedWhite: 0.94, alpha: 0.92)
}
static func resolvedOnboardingAssistantBubbleColor(for appearance: NSAppearance) -> NSColor {
appearance.isDarkAqua
? NSColor(calibratedWhite: 0.20, alpha: 0.94)
: NSColor(calibratedWhite: 0.97, alpha: 0.98)
}
static let assistantBubbleDynamicNSColor = NSColor(
name: NSColor.Name("ClawdisChatTheme.assistantBubble"),
dynamicProvider: resolvedAssistantBubbleColor(for:))
static let onboardingAssistantBubbleDynamicNSColor = NSColor(
name: NSColor.Name("ClawdisChatTheme.onboardingAssistantBubble"),
dynamicProvider: resolvedOnboardingAssistantBubbleColor(for:))
#endif
static var surface: Color {
#if os(macOS)
Color(nsColor: .windowBackgroundColor)
@@ -78,9 +110,7 @@ enum ClawdisChatTheme {
static var assistantBubble: Color {
#if os(macOS)
let base = NSColor.controlBackgroundColor
let blended = base.blended(withFraction: 0.18, of: .white) ?? base
return Color(nsColor: blended).opacity(0.88)
Color(nsColor: self.assistantBubbleDynamicNSColor)
#else
Color(uiColor: .secondarySystemBackground)
#endif
@@ -88,9 +118,7 @@ enum ClawdisChatTheme {
static var onboardingAssistantBubble: Color {
#if os(macOS)
let base = NSColor.controlBackgroundColor
let blended = base.blended(withFraction: 0.22, of: .white) ?? base
return Color(nsColor: blended)
Color(nsColor: self.onboardingAssistantBubbleDynamicNSColor)
#else
Color(uiColor: .secondarySystemBackground)
#endif

View File

@@ -0,0 +1,29 @@
@testable import ClawdisChatUI
import Foundation
import Testing
#if os(macOS)
import AppKit
#endif
#if os(macOS)
private func luminance(_ color: NSColor) throws -> CGFloat {
let rgb = try #require(color.usingColorSpace(.deviceRGB))
return 0.2126 * rgb.redComponent + 0.7152 * rgb.greenComponent + 0.0722 * rgb.blueComponent
}
#endif
@Suite struct ChatThemeTests {
@Test func assistantBubbleResolvesForLightAndDark() throws {
#if os(macOS)
let lightAppearance = try #require(NSAppearance(named: .aqua))
let darkAppearance = try #require(NSAppearance(named: .darkAqua))
let lightResolved = ClawdisChatTheme.resolvedAssistantBubbleColor(for: lightAppearance)
let darkResolved = ClawdisChatTheme.resolvedAssistantBubbleColor(for: darkAppearance)
#expect(try luminance(lightResolved) > luminance(darkResolved))
#else
#expect(Bool(true))
#endif
}
}