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:
Trey t
2026-02-19 09:23:29 -06:00
parent dad3270be7
commit e7420061a5
12 changed files with 180 additions and 101 deletions

View File

@@ -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.