mac: @MainActor-gate ChatTextSize's UserDefaults writers (#1200)
ChatTextSize.bump/reset were nonisolated static funcs that write UserDefaults.standard, so their main-thread safety rested on call-site discipline rather than the compiler. Every caller (View-menu commands, the ⌘= key monitor) already runs on the main thread, but nothing stopped a future off-main caller — the same UserDefaults concurrent-write hazard that caused the CFBasicHash crash fixed for shared code in #1194/#1196. Mark the two writers @MainActor so the guarantee is compiler-enforced. Pure reads/constants (current, snap, stops) stay nonisolated. No call-site changes needed; Mac app builds clean.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 file changed,
+7
−2
ios/LChatMac/TextSize.swift+7 −2
diff --git a/ios/LChatMac/TextSize.swift b/ios/LChatMac/TextSize.swift| index 6003f0e..ed2cc48 100644 |
| --- a/ios/LChatMac/TextSize.swift |
| +++ b/ios/LChatMac/TextSize.swift |
| @@ -23,13 +23,18 @@ enum ChatTextSize { |
| stops.min(by: { abs($0 - v) < abs($1 - v) }) ?? defaultSize |
| } |
| - static func bump(_ direction: Int) { |
| + // @MainActor on the two WRITERS (not the pure reads/constants): UserDefaults |
| + // is process-global and unsafe against concurrent writes from two threads, so |
| + // every writer must stay on the main actor. All callers already do (View-menu |
| + // commands, the ⌘= key monitor); this makes that a compiler-enforced guarantee |
| + // rather than call-site discipline. |
| + @MainActor static func bump(_ direction: Int) { |
| guard let i = stops.firstIndex(of: current) else { return } |
| let j = min(max(i + direction, 0), stops.count - 1) |
| UserDefaults.standard.set(stops[j], forKey: key) |
| } |
| - static func reset() { |
| + @MainActor static func reset() { |
| UserDefaults.standard.set(defaultSize, forKey: key) |
| } |
| } |