fix(webchat): refresh bubbles on theme change
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
- System prompt now tags allowlisted owner numbers as the user identity to avoid mistaken “friend” assumptions.
|
||||
- LM Studio/Ollama replies now require <final> tags; streaming ignores content until <final> begins.
|
||||
- `process log` pagination is now line-based (omit `offset` to grab the last N lines).
|
||||
- macOS WebChat: assistant bubbles now update correctly when toggling light/dark mode.
|
||||
- macOS: avoid spawning a duplicate gateway process when an external listener already exists.
|
||||
- Node bridge: when binding to a non-loopback host (e.g. Tailnet IP), also listens on `127.0.0.1` for local connections (without creating duplicate loopback listeners for `0.0.0.0`/`127.0.0.1` binds).
|
||||
- UI perf: pause repeat animations when scenes are inactive (typing dots, onboarding glow, iOS status pulse), throttle voice overlay level updates, and reduce overlay focus churn.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user