fix: 22 audit fixes — concurrency, memory, performance, accessibility
- Move 7 Data(contentsOf:) calls off MainActor via Task.detached (BootstrapService) - Batch-fetch N+1 queries in sync merge loops (CanonicalSyncService) - Predicate-based gamesForTeam fetch instead of fetching all games (DataProvider) - Proper Sendable on RouteInfo with nonisolated(unsafe) polyline (LocationService) - [weak self] in BGTaskScheduler register closures (BackgroundSyncManager) - Cache tripDays, routeWaypoints as @State with recompute (TripDetailView) - Remove unused AnyCancellable, add Task lifecycle management (TripDetailView) - Cache sportStadiums, recentVisits as stored properties (ProgressViewModel) - Dynamic Type fonts replacing hardcoded sizes (OnboardingPaywallView) - Accessibility labels/hints on stadium picker, date picker, map, stats, settings toggle, and day cards Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -289,20 +289,25 @@ final class AppDataProvider: ObservableObject {
|
||||
throw DataProviderError.contextNotConfigured
|
||||
}
|
||||
|
||||
// Fetch all non-deprecated games (predicate with captured vars causes type-check timeout)
|
||||
let descriptor = FetchDescriptor<CanonicalGame>(
|
||||
predicate: #Predicate<CanonicalGame> { $0.deprecatedAt == nil },
|
||||
// Use two separate predicates to avoid the type-check timeout from combining
|
||||
// captured vars with OR in a single #Predicate expression.
|
||||
let homeDescriptor = FetchDescriptor<CanonicalGame>(
|
||||
predicate: #Predicate<CanonicalGame> { $0.deprecatedAt == nil && $0.homeTeamCanonicalId == teamId },
|
||||
sortBy: [SortDescriptor(\.dateTime)]
|
||||
)
|
||||
let awayDescriptor = FetchDescriptor<CanonicalGame>(
|
||||
predicate: #Predicate<CanonicalGame> { $0.deprecatedAt == nil && $0.awayTeamCanonicalId == teamId },
|
||||
sortBy: [SortDescriptor(\.dateTime)]
|
||||
)
|
||||
|
||||
let allCanonical: [CanonicalGame] = try context.fetch(descriptor)
|
||||
let homeGames = try context.fetch(homeDescriptor)
|
||||
let awayGames = try context.fetch(awayDescriptor)
|
||||
|
||||
// Filter by team in Swift (fast for ~5K games)
|
||||
// Merge and deduplicate
|
||||
var seenIds = Set<String>()
|
||||
var teamGames: [RichGame] = []
|
||||
for canonical in allCanonical {
|
||||
guard canonical.homeTeamCanonicalId == teamId || canonical.awayTeamCanonicalId == teamId else {
|
||||
continue
|
||||
}
|
||||
for canonical in homeGames + awayGames {
|
||||
guard seenIds.insert(canonical.canonicalId).inserted else { continue }
|
||||
let game = canonical.toDomain()
|
||||
guard let homeTeam = teamsById[game.homeTeamId],
|
||||
let awayTeam = teamsById[game.awayTeamId],
|
||||
@@ -311,7 +316,7 @@ final class AppDataProvider: ObservableObject {
|
||||
}
|
||||
teamGames.append(RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium))
|
||||
}
|
||||
return teamGames
|
||||
return teamGames.sorted { $0.game.dateTime < $1.game.dateTime }
|
||||
}
|
||||
|
||||
// Resolve stadium defensively: direct game reference first, then team home-venue fallbacks.
|
||||
|
||||
Reference in New Issue
Block a user