Fix 25 audit issues: memory leaks, concurrency, performance, accessibility

Address findings from comprehensive audit across 5 workstreams:

- Memory: Token-based DataController listeners (prevent closure leaks),
  static DateFormatters, ImageCache observer cleanup, MotionManager
  reference counting, FoundationModels dedup guard
- Concurrency: Replace Task.detached with Task in FeelsApp (preserve
  MainActor isolation), wrap WatchConnectivity handler in MainActor
- Performance: Cache sortedGroupedData in DayViewViewModel, cache demo
  data in MonthView/YearView, remove broken ReduceMotionModifier
- Accessibility: VoiceOver support for LockScreen, DemoHeatmapCell
  labels, MonthCard button labels, InsightsView header traits,
  Smart Invert protection on neon headers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-19 09:11:48 -06:00
parent b58dfd5093
commit c22d246865
18 changed files with 175 additions and 73 deletions

View File

@@ -22,7 +22,8 @@ final class DataController: ObservableObject {
// Listeners for data changes (keeping existing pattern)
private var editedDataClosure = [() -> Void]()
typealias DataListenerToken = UUID
private var dataListeners: [DataListenerToken: () -> Void] = [:]
// Computed properties for earliest/latest entries
var earliestEntry: MoodEntryModel? {
@@ -48,15 +49,22 @@ final class DataController: ObservableObject {
// MARK: - Listener Management
func addNewDataListener(closure: @escaping (() -> Void)) {
editedDataClosure.append(closure)
@discardableResult
func addNewDataListener(closure: @escaping (() -> Void)) -> DataListenerToken {
let token = DataListenerToken()
dataListeners[token] = closure
return token
}
func removeDataListener(token: DataListenerToken) {
dataListeners.removeValue(forKey: token)
}
@discardableResult
func saveAndRunDataListeners() -> Bool {
let success = save()
if success {
for closure in editedDataClosure {
for closure in dataListeners.values {
closure()
}
}
@@ -91,7 +99,7 @@ final class DataController: ObservableObject {
modelContext.rollback()
// Notify listeners to re-fetch their data
for closure in editedDataClosure {
for closure in dataListeners.values {
closure()
}

View File

@@ -77,8 +77,12 @@ protocol MoodDataPersisting {
@discardableResult
func saveAndRunDataListeners() -> Bool
/// Add a listener for data changes
func addNewDataListener(closure: @escaping (() -> Void))
/// Add a listener for data changes, returns a token for removal
@discardableResult
func addNewDataListener(closure: @escaping (() -> Void)) -> DataController.DataListenerToken
/// Remove a previously registered data listener
func removeDataListener(token: DataController.DataListenerToken)
}
/// Combined protocol for full data controller functionality