spb/zyquo-agent Public MIT
The autonomous agent that actually operates your Mac — plans, runs real commands, verifies its own work.
Swift 94.7%
Shell 4.1%
Python 0.7%
Makefile 0.5%
1//2// AppearanceStore.swift3// Zyquo Agent4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// User appearance preferences: theme mode, accent choice, chat font size.9// Persisted to settings.json via PersistenceService.10//1112import SwiftUI1314/// Accent color choices offered in Settings → Appearance. Violet is the15/// Agent flagship default; alternates keep the same subtle-tint relationship16/// (graphite, sky, emerald, amber per the Phase 4 spec).17enum AccentChoice: String, Codable, CaseIterable, Identifiable {18 case violet19 case graphite20 case sky21 case emerald22 case amber2324 var id: String { rawValue }2526 var displayName: String {27 switch self {28 case .violet: return "Violet"29 case .graphite: return "Graphite"30 case .sky: return "Sky"31 case .emerald: return "Emerald"32 case .amber: return "Amber"33 }34 }3536 /// (light, dark) accent hex pair.37 var accentHex: (UInt32, UInt32) {38 switch self {39 case .violet: return (0x7A5AF0, 0x9B82F6)40 case .graphite: return (0x5B6270, 0x8A93A6)41 case .sky: return (0x4E6AF0, 0x6D84F5)42 case .emerald: return (0x1D9A6B, 0x3BB58A)43 case .amber: return (0xC77D1D, 0xDD9B45)44 }45 }4647 /// (light, dark) subtle-tint hex pair (selected rows, user bubbles).48 var subtleHex: (UInt32, UInt32) {49 switch self {50 case .violet: return (0xEFEBFD, 0x2E2749)51 case .graphite: return (0xEEF0F4, 0x2A2E3A)52 case .sky: return (0xEBEFFD, 0x28304C)53 case .emerald: return (0xE6F6EF, 0x1F3A2F)54 case .amber: return (0xFBF1E3, 0x3D3222)55 }56 }57}5859enum ThemeMode: String, Codable, CaseIterable, Identifiable {60 case system, light, dark61 var id: String { rawValue }6263 var displayName: String {64 switch self {65 case .system: return "System"66 case .light: return "Light"67 case .dark: return "Dark"68 }69 }7071 var colorScheme: ColorScheme? {72 switch self {73 case .system: return nil74 case .light: return .light75 case .dark: return .dark76 }77 }78}7980/// Observable appearance preferences, persisted as part of app settings.81@MainActor82final class AppearanceStore: ObservableObject {83 struct Stored: Codable {84 var themeMode: ThemeMode = .system85 var accent: AccentChoice = .violet86 var chatFontSize: Double = 13.587 }8889 static let fileName = "appearance.json"9091 @Published var themeMode: ThemeMode { didSet { save() } }92 @Published var accent: AccentChoice {93 didSet {94 // Feed the design-system resolver so the static ZyquoColor.accent95 // token picks up the new choice on the next draw.96 ZyquoAccentResolver.current = accent97 save()98 }99 }100 /// Chat body font size, clamped to the spec's 12–18pt range.101 @Published var chatFontSize: Double {102 didSet {103 let clamped = min(18, max(12, chatFontSize))104 if clamped != chatFontSize { chatFontSize = clamped }105 save()106 }107 }108109 private let persistence: PersistenceService110111 init(persistence: PersistenceService = .shared) {112 self.persistence = persistence113 let stored = persistence.load(Stored.self, from: Self.fileName) ?? Stored()114 themeMode = stored.themeMode115 accent = stored.accent116 chatFontSize = stored.chatFontSize117 ZyquoAccentResolver.current = stored.accent118 }119120 /// Current accent color resolved for the active appearance.121 var accentColor: Color {122 let (light, dark) = accent.accentHex123 return Color(nsColor: NSColor(name: nil) { appearance in124 let hex = appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua ? dark : light125 return NSColor(hex: hex)126 })127 }128129 /// Current subtle accent tint resolved for the active appearance.130 var accentSubtleColor: Color {131 let (light, dark) = accent.subtleHex132 return Color(nsColor: NSColor(name: nil) { appearance in133 let hex = appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua ? dark : light134 return NSColor(hex: hex)135 })136 }137138 private func save() {139 persistence.save(140 Stored(themeMode: themeMode, accent: accent, chatFontSize: chatFontSize),141 to: Self.fileName142 )143 }144}145