| 1 | import Foundation |
| 2 | |
| 3 | // 🤖 The Mac's chat Text Size preference, in points — the same seven-stop |
| 4 | // ladder as the web slider (TEXT_SIZES in ui.js). The default sits ONE FROM |
| 5 | // THE TOP: 16 is the only workable bigger size (beyond that bubbles read as |
| 6 | // shouting), so the room to taste lies below. One UserDefaults key that |
| 7 | // @AppStorage views observe; the View-menu commands, the ⌘= key monitor and |
| 8 | // the Settings slider all step through here so every entry point moves the |
| 9 | // same ladder. |
| 10 | enum ChatTextSize { |
| 11 | static let stops: [Double] = [10, 11, 12, 13, 14, 15, 16] |
| 12 | static let defaultSize: Double = 15 |
| 13 | static let key = "chatTextSize" |
| 14 | |
| 15 | static var current: Double { |
| 16 | let v = UserDefaults.standard.double(forKey: key) |
| 17 | return v > 0 ? snap(v) : defaultSize |
| 18 | } |
| 19 | |
| 20 | // Nearest stop, so a value stored by a future (or hand-edited) ladder |
| 21 | // still lands somewhere sane. |
| 22 | static func snap(_ v: Double) -> Double { |
| 23 | stops.min(by: { abs($0 - v) < abs($1 - v) }) ?? defaultSize |
| 24 | } |
| 25 | |
| 26 | // @MainActor on the two WRITERS (not the pure reads/constants): UserDefaults |
| 27 | // is process-global and unsafe against concurrent writes from two threads, so |
| 28 | // every writer must stay on the main actor. All callers already do (View-menu |
| 29 | // commands, the ⌘= key monitor); this makes that a compiler-enforced guarantee |
| 30 | // rather than call-site discipline. |
| 31 | @MainActor static func bump(_ direction: Int) { |
| 32 | guard let i = stops.firstIndex(of: current) else { return } |
| 33 | let j = min(max(i + direction, 0), stops.count - 1) |
| 34 | UserDefaults.standard.set(stops[j], forKey: key) |
| 35 | } |
| 36 | |
| 37 | @MainActor static func reset() { |
| 38 | UserDefaults.standard.set(defaultSize, forKey: key) |
| 39 | } |
| 40 | } |
| 41 | |