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

@@ -237,6 +237,7 @@ struct InsightsSectionView: View {
.padding(.vertical, 14)
}
.buttonStyle(.plain)
.accessibilityAddTraits(.isHeader)
// Insights List (collapsible)
if isExpanded {
@@ -415,6 +416,7 @@ struct InsightCardView: View {
RoundedRectangle(cornerRadius: 12)
.fill(colorScheme == .dark ? Color(.systemGray5) : Color(.systemGray6))
)
.accessibilityElement(children: .combine)
}
}

View File

@@ -49,14 +49,24 @@ class InsightsViewModel: ObservableObject {
// MARK: - Initialization
private var dataListenerToken: DataController.DataListenerToken?
init() {
isAIAvailable = insightService.isAvailable
DataController.shared.addNewDataListener { [weak self] in
dataListenerToken = DataController.shared.addNewDataListener { [weak self] in
self?.onDataChanged()
}
}
deinit {
if let token = dataListenerToken {
Task { @MainActor in
DataController.shared.removeDataListener(token: token)
}
}
}
/// Called when mood data changes in another tab. Invalidates cached insights
/// so they are regenerated with fresh data on next view appearance.
private func onDataChanged() {