fix: 14 audit fixes — concurrency, memory, performance, accessibility

Second audit round addressing data races, task stacking, unbounded
caches, and VoiceOver gaps across 7 files.

Concurrency:
- Move NSItemProvider @State access into MainActor block (3 drop handlers)
- Cancel stale ScheduleViewModel tasks on rapid filter changes

Memory:
- Replace unbounded image dict with LRUCache(countLimit: 50)
- Replace demo-mode asyncAfter with cancellable Task

Performance:
- Wrap debug NBA print() in #if DEBUG
- Cache visitsById via @State + onChange instead of per-render computed
- Pre-compute sportProgressFractions in ProgressViewModel
- Replace allGames computed property with hasGames bool check
- Cache sortedTrips via @State + onChange in SavedTripsListView

Accessibility:
- Add combined VoiceOver label to progress ring
- Combine away/home team text into single readable phrase
- Hide decorative StadiumDetailSheet icon from VoiceOver
- Add explicit accessibilityLabel to SportFilterChip
- Add combined accessibilityLabel to GameRowView

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-18 22:30:30 -06:00
parent c32a08a49e
commit d0cbf75fc4
7 changed files with 75 additions and 48 deletions

View File

@@ -7,6 +7,7 @@
import Foundation
import UIKit
import LRUCache
actor RemoteImageService {
@@ -32,7 +33,7 @@ actor RemoteImageService {
// MARK: - Properties
private let urlSession: URLSession
private var imageCache: [URL: UIImage] = [:]
private let imageCache = LRUCache<URL, UIImage>(countLimit: 50)
// MARK: - Init
@@ -56,7 +57,7 @@ actor RemoteImageService {
/// Fetch a single image from URL
func fetchImage(from url: URL) async throws -> UIImage {
// Check cache first
if let cached = imageCache[url] {
if let cached = imageCache.value(forKey: url) {
return cached
}
@@ -79,7 +80,7 @@ actor RemoteImageService {
let scaledImage = scaleImage(image, maxWidth: 400)
// Cache it
imageCache[url] = scaledImage
imageCache.setValue(scaledImage, forKey: url)
return scaledImage
}