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

@@ -21,25 +21,7 @@ extension EnvironmentValues {
}
}
/// View modifier that respects reduce motion preference
struct ReduceMotionModifier: ViewModifier {
@Environment(\.accessibilityReduceMotion) var reduceMotion
let animation: Animation?
let reducedAnimation: Animation?
func body(content: Content) -> some View {
content
.animation(reduceMotion ? reducedAnimation : animation, value: UUID())
}
}
extension View {
/// Applies animation only when reduce motion is disabled
func accessibleAnimation(_ animation: Animation? = .default, reduced: Animation? = nil) -> some View {
modifier(ReduceMotionModifier(animation: animation, reducedAnimation: reduced))
}
/// Wraps content in withAnimation respecting reduce motion
func withAccessibleAnimation<V: Equatable>(_ animation: Animation? = .default, value: V, action: @escaping () -> Void) -> some View {
self.onChange(of: value) { _, _ in
@@ -59,11 +41,10 @@ extension View {
extension View {
/// Adds accessibility label with optional hint
func accessibleMoodCell(mood: Mood, date: Date) -> some View {
let formatter = DateFormatter()
formatter.dateStyle = .medium
let dateString = DateFormattingCache.shared.string(for: date, format: .dateMedium)
return self
.accessibilityLabel("\(mood.strValue) on \(formatter.string(from: date))")
.accessibilityLabel("\(mood.strValue) on \(dateString)")
.accessibilityHint("Double tap to edit mood")
}