SPB Git

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%
4.3 KB · 145 lines swift
Raw Blame History
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