fix(schedule): use start of day for date range queries

- Fix startDate to use Calendar.startOfDay instead of Date() to include
  games earlier in the current day
- Add SyncLogger for file-based sync logging viewable in Settings
- Add "View Sync Logs" button in Settings debug section
- Add diagnostics and NBA game logging to ScheduleViewModel
- Add dropped game logging to DataProvider.filterRichGames
- Use SyncLogger in SportsTimeApp and CloudKitService for sync operations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-20 22:25:44 -06:00
parent 8ea3e6112a
commit 87079b434d
6 changed files with 355 additions and 19 deletions

View File

@@ -151,6 +151,7 @@ struct BootstrappedContentView: View {
@MainActor
private func performBootstrap() async {
print("🚀 [BOOT] Starting app bootstrap...")
isBootstrapping = true
bootstrapError = nil
@@ -159,34 +160,44 @@ struct BootstrappedContentView: View {
do {
// 1. Bootstrap from bundled JSON if first launch (no data exists)
print("🚀 [BOOT] Step 1: Checking if bootstrap needed...")
try await bootstrapService.bootstrapIfNeeded(context: context)
// 2. Configure DataProvider with SwiftData context
print("🚀 [BOOT] Step 2: Configuring DataProvider...")
AppDataProvider.shared.configure(with: context)
// 3. Configure BackgroundSyncManager with model container
print("🚀 [BOOT] Step 3: Configuring BackgroundSyncManager...")
BackgroundSyncManager.shared.configure(with: modelContainer)
// 4. Load data from SwiftData into memory
print("🚀 [BOOT] Step 4: Loading initial data from SwiftData...")
await AppDataProvider.shared.loadInitialData()
print("🚀 [BOOT] Loaded \(AppDataProvider.shared.teams.count) teams")
print("🚀 [BOOT] Loaded \(AppDataProvider.shared.stadiums.count) stadiums")
// 5. Load store products and entitlements
print("🚀 [BOOT] Step 5: Loading store products...")
await StoreManager.shared.loadProducts()
await StoreManager.shared.updateEntitlements()
// 6. Start network monitoring and wire up sync callback
print("🚀 [BOOT] Step 6: Starting network monitoring...")
NetworkMonitor.shared.onSyncNeeded = {
await BackgroundSyncManager.shared.triggerSyncFromNetworkRestoration()
}
NetworkMonitor.shared.startMonitoring()
// 7. App is now usable
print("🚀 [BOOT] Step 7: Bootstrap complete - app ready")
isBootstrapping = false
// 8. Schedule background tasks for future syncs
BackgroundSyncManager.shared.scheduleAllTasks()
// 9. Background: Try to refresh from CloudKit (non-blocking)
print("🚀 [BOOT] Step 9: Starting background CloudKit sync...")
Task.detached(priority: .background) {
await self.performBackgroundSync(context: context)
await MainActor.run {
@@ -194,6 +205,7 @@ struct BootstrappedContentView: View {
}
}
} catch {
print("❌ [BOOT] Bootstrap failed: \(error.localizedDescription)")
bootstrapError = error
isBootstrapping = false
}
@@ -201,22 +213,46 @@ struct BootstrappedContentView: View {
@MainActor
private func performBackgroundSync(context: ModelContext) async {
let log = SyncLogger.shared
log.log("🔄 [SYNC] Starting background sync...")
// Reset stale syncInProgress flag (in case app was killed mid-sync)
let syncState = SyncState.current(in: context)
if syncState.syncInProgress {
log.log("⚠️ [SYNC] Resetting stale syncInProgress flag")
syncState.syncInProgress = false
try? context.save()
}
let syncService = CanonicalSyncService()
do {
let result = try await syncService.syncAll(context: context)
log.log("🔄 [SYNC] Sync completed in \(String(format: "%.2f", result.duration))s")
log.log("🔄 [SYNC] Stadiums: \(result.stadiumsUpdated)")
log.log("🔄 [SYNC] Teams: \(result.teamsUpdated)")
log.log("🔄 [SYNC] Games: \(result.gamesUpdated)")
log.log("🔄 [SYNC] League Structures: \(result.leagueStructuresUpdated)")
log.log("🔄 [SYNC] Team Aliases: \(result.teamAliasesUpdated)")
log.log("🔄 [SYNC] Stadium Aliases: \(result.stadiumAliasesUpdated)")
log.log("🔄 [SYNC] Sports: \(result.sportsUpdated)")
log.log("🔄 [SYNC] Skipped (incompatible): \(result.skippedIncompatible)")
log.log("🔄 [SYNC] Skipped (older): \(result.skippedOlder)")
log.log("🔄 [SYNC] Total updated: \(result.totalUpdated)")
// If any data was updated, reload the DataProvider
if !result.isEmpty {
log.log("🔄 [SYNC] Reloading DataProvider...")
await AppDataProvider.shared.loadInitialData()
print("CloudKit sync completed: \(result.totalUpdated) items updated")
log.log("🔄 [SYNC] DataProvider reloaded. Teams: \(AppDataProvider.shared.teams.count), Stadiums: \(AppDataProvider.shared.stadiums.count)")
} else {
log.log("🔄 [SYNC] No updates - skipping DataProvider reload")
}
} catch CanonicalSyncService.SyncError.cloudKitUnavailable {
// Offline or CloudKit not available - silently continue with local data
print("CloudKit unavailable, using local data")
log.log("❌ [SYNC] CloudKit unavailable - using local data only")
} catch {
// Other sync errors - log but don't interrupt user
print("Background sync error: \(error.localizedDescription)")
log.log("❌ [SYNC] Error: \(error.localizedDescription)")
}
}