docs: add delta sync implementation plan
16-task TDD implementation plan for: - CloudKit delta sync using modificationDate - Remove 90-day game browsing limit - Rename fetch* to filter* for clarity - Add allGames/allRichGames methods Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
680
docs/plans/2026-01-12-delta-sync-implementation.md
Normal file
680
docs/plans/2026-01-12-delta-sync-implementation.md
Normal file
@@ -0,0 +1,680 @@
|
||||
# Delta Sync Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Remove arbitrary date restrictions from game browsing and implement proper delta sync using CloudKit modificationDate.
|
||||
|
||||
**Architecture:** Update CloudKitService to query by `modificationDate` instead of game `dateTime`. First sync fetches all records, subsequent syncs fetch only modified records. Rename DataProvider methods to clarify local vs network semantics.
|
||||
|
||||
**Tech Stack:** Swift, SwiftData, CloudKit, XCTest
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Update CloudKitService.fetchStadiumsForSync
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Core/Services/CloudKitService.swift:217-231`
|
||||
|
||||
**Step 1: Update method signature and implementation**
|
||||
|
||||
Change from:
|
||||
```swift
|
||||
func fetchStadiumsForSync() async throws -> [SyncStadium]
|
||||
```
|
||||
|
||||
To:
|
||||
```swift
|
||||
/// Fetch stadiums for sync operations
|
||||
/// - Parameter lastSync: If nil, fetches all stadiums. If provided, fetches only stadiums modified since that date.
|
||||
func fetchStadiumsForSync(since lastSync: Date?) async throws -> [SyncStadium] {
|
||||
let predicate: NSPredicate
|
||||
if let lastSync = lastSync {
|
||||
predicate = NSPredicate(format: "modificationDate >= %@", lastSync as NSDate)
|
||||
} else {
|
||||
predicate = NSPredicate(value: true)
|
||||
}
|
||||
let query = CKQuery(recordType: CKRecordType.stadium, predicate: predicate)
|
||||
|
||||
let (results, _) = try await publicDatabase.records(matching: query)
|
||||
|
||||
return results.compactMap { result -> SyncStadium? in
|
||||
guard case .success(let record) = result.1 else { return nil }
|
||||
let ckStadium = CKStadium(record: record)
|
||||
guard let stadium = ckStadium.stadium,
|
||||
let canonicalId = ckStadium.canonicalId
|
||||
else { return nil }
|
||||
return SyncStadium(stadium: stadium, canonicalId: canonicalId)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Core/Services/CloudKitService.swift
|
||||
git commit -m "feat(sync): add lastSync parameter to fetchStadiumsForSync"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Update CloudKitService.fetchTeamsForSync
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Core/Services/CloudKitService.swift:233-249`
|
||||
|
||||
**Step 1: Update method signature and implementation**
|
||||
|
||||
Change from per-sport to all teams with delta sync:
|
||||
```swift
|
||||
/// Fetch teams for sync operations
|
||||
/// - Parameter lastSync: If nil, fetches all teams. If provided, fetches only teams modified since that date.
|
||||
func fetchTeamsForSync(since lastSync: Date?) async throws -> [SyncTeam] {
|
||||
let predicate: NSPredicate
|
||||
if let lastSync = lastSync {
|
||||
predicate = NSPredicate(format: "modificationDate >= %@", lastSync as NSDate)
|
||||
} else {
|
||||
predicate = NSPredicate(value: true)
|
||||
}
|
||||
let query = CKQuery(recordType: CKRecordType.team, predicate: predicate)
|
||||
|
||||
let (results, _) = try await publicDatabase.records(matching: query)
|
||||
|
||||
return results.compactMap { result -> SyncTeam? in
|
||||
guard case .success(let record) = result.1 else { return nil }
|
||||
let ckTeam = CKTeam(record: record)
|
||||
guard let team = ckTeam.team,
|
||||
let canonicalId = ckTeam.canonicalId,
|
||||
let stadiumCanonicalId = ckTeam.stadiumCanonicalId
|
||||
else { return nil }
|
||||
return SyncTeam(team: team, canonicalId: canonicalId, stadiumCanonicalId: stadiumCanonicalId)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Core/Services/CloudKitService.swift
|
||||
git commit -m "feat(sync): change fetchTeamsForSync to delta sync (all teams, not per-sport)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Update CloudKitService.fetchGamesForSync
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Core/Services/CloudKitService.swift:251-301`
|
||||
|
||||
**Step 1: Update method signature and implementation**
|
||||
|
||||
Change from date range to delta sync:
|
||||
```swift
|
||||
/// Fetch games for sync operations
|
||||
/// - Parameter lastSync: If nil, fetches all games. If provided, fetches only games modified since that date.
|
||||
func fetchGamesForSync(since lastSync: Date?) async throws -> [SyncGame] {
|
||||
let predicate: NSPredicate
|
||||
if let lastSync = lastSync {
|
||||
predicate = NSPredicate(format: "modificationDate >= %@", lastSync as NSDate)
|
||||
} else {
|
||||
predicate = NSPredicate(value: true)
|
||||
}
|
||||
let query = CKQuery(recordType: CKRecordType.game, predicate: predicate)
|
||||
|
||||
let (results, _) = try await publicDatabase.records(matching: query)
|
||||
|
||||
return results.compactMap { result -> SyncGame? in
|
||||
guard case .success(let record) = result.1 else { return nil }
|
||||
let ckGame = CKGame(record: record)
|
||||
|
||||
guard let canonicalId = ckGame.canonicalId,
|
||||
let homeTeamCanonicalId = ckGame.homeTeamCanonicalId,
|
||||
let awayTeamCanonicalId = ckGame.awayTeamCanonicalId,
|
||||
let stadiumCanonicalId = ckGame.stadiumCanonicalId
|
||||
else { return nil }
|
||||
|
||||
guard let game = ckGame.game(
|
||||
homeTeamId: homeTeamCanonicalId,
|
||||
awayTeamId: awayTeamCanonicalId,
|
||||
stadiumId: stadiumCanonicalId
|
||||
) else { return nil }
|
||||
|
||||
return SyncGame(
|
||||
game: game,
|
||||
canonicalId: canonicalId,
|
||||
homeTeamCanonicalId: homeTeamCanonicalId,
|
||||
awayTeamCanonicalId: awayTeamCanonicalId,
|
||||
stadiumCanonicalId: stadiumCanonicalId
|
||||
)
|
||||
}.sorted { $0.game.dateTime < $1.game.dateTime }
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Core/Services/CloudKitService.swift
|
||||
git commit -m "feat(sync): change fetchGamesForSync to delta sync by modificationDate"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Update CanonicalSyncService.syncStadiums
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Core/Services/CanonicalSyncService.swift:208-236`
|
||||
|
||||
**Step 1: Pass lastSync to CloudKit fetch**
|
||||
|
||||
Change line 214 from:
|
||||
```swift
|
||||
let syncStadiums = try await cloudKitService.fetchStadiumsForSync()
|
||||
```
|
||||
|
||||
To:
|
||||
```swift
|
||||
let syncStadiums = try await cloudKitService.fetchStadiumsForSync(since: lastSync)
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Core/Services/CanonicalSyncService.swift
|
||||
git commit -m "feat(sync): pass lastSync to stadium sync for delta updates"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Update CanonicalSyncService.syncTeams
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Core/Services/CanonicalSyncService.swift:238-271`
|
||||
|
||||
**Step 1: Simplify to single CloudKit call**
|
||||
|
||||
Replace the for-loop that calls per-sport:
|
||||
```swift
|
||||
@MainActor
|
||||
private func syncTeams(
|
||||
context: ModelContext,
|
||||
since lastSync: Date?
|
||||
) async throws -> (updated: Int, skippedIncompatible: Int, skippedOlder: Int) {
|
||||
// Single call for all teams (no per-sport loop)
|
||||
let allSyncTeams = try await cloudKitService.fetchTeamsForSync(since: lastSync)
|
||||
|
||||
var updated = 0
|
||||
var skippedIncompatible = 0
|
||||
var skippedOlder = 0
|
||||
|
||||
for syncTeam in allSyncTeams {
|
||||
let result = try mergeTeam(
|
||||
syncTeam.team,
|
||||
canonicalId: syncTeam.canonicalId,
|
||||
stadiumCanonicalId: syncTeam.stadiumCanonicalId,
|
||||
context: context
|
||||
)
|
||||
|
||||
switch result {
|
||||
case .applied: updated += 1
|
||||
case .skippedIncompatible: skippedIncompatible += 1
|
||||
case .skippedOlder: skippedOlder += 1
|
||||
}
|
||||
}
|
||||
|
||||
return (updated, skippedIncompatible, skippedOlder)
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Core/Services/CanonicalSyncService.swift
|
||||
git commit -m "feat(sync): simplify team sync to single CloudKit call with delta"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Update CanonicalSyncService.syncGames
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Core/Services/CanonicalSyncService.swift:273-311`
|
||||
|
||||
**Step 1: Remove date range, use delta sync**
|
||||
|
||||
Replace lines 278-286:
|
||||
```swift
|
||||
@MainActor
|
||||
private func syncGames(
|
||||
context: ModelContext,
|
||||
since lastSync: Date?
|
||||
) async throws -> (updated: Int, skippedIncompatible: Int, skippedOlder: Int) {
|
||||
// Delta sync: nil = all games, Date = only modified since
|
||||
let syncGames = try await cloudKitService.fetchGamesForSync(since: lastSync)
|
||||
|
||||
var updated = 0
|
||||
var skippedIncompatible = 0
|
||||
var skippedOlder = 0
|
||||
|
||||
for syncGame in syncGames {
|
||||
let result = try mergeGame(
|
||||
syncGame.game,
|
||||
canonicalId: syncGame.canonicalId,
|
||||
homeTeamCanonicalId: syncGame.homeTeamCanonicalId,
|
||||
awayTeamCanonicalId: syncGame.awayTeamCanonicalId,
|
||||
stadiumCanonicalId: syncGame.stadiumCanonicalId,
|
||||
context: context
|
||||
)
|
||||
|
||||
switch result {
|
||||
case .applied: updated += 1
|
||||
case .skippedIncompatible: skippedIncompatible += 1
|
||||
case .skippedOlder: skippedOlder += 1
|
||||
}
|
||||
}
|
||||
|
||||
return (updated, skippedIncompatible, skippedOlder)
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Core/Services/CanonicalSyncService.swift
|
||||
git commit -m "feat(sync): remove date range from game sync, use modificationDate delta"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Rename DataProvider.fetchGames to filterGames
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Core/Services/DataProvider.swift:121-144`
|
||||
|
||||
**Step 1: Rename method**
|
||||
|
||||
Change:
|
||||
```swift
|
||||
/// Fetch games from SwiftData within date range
|
||||
func fetchGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [Game]
|
||||
```
|
||||
|
||||
To:
|
||||
```swift
|
||||
/// Filter games from SwiftData within date range
|
||||
func filterGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [Game]
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Core/Services/DataProvider.swift
|
||||
git commit -m "refactor: rename fetchGames to filterGames (clarify local query)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Rename DataProvider.fetchRichGames to filterRichGames
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Core/Services/DataProvider.swift:163-175`
|
||||
|
||||
**Step 1: Rename method and update internal call**
|
||||
|
||||
Change:
|
||||
```swift
|
||||
/// Fetch games with full team and stadium data
|
||||
func fetchRichGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [RichGame] {
|
||||
let games = try await fetchGames(sports: sports, startDate: startDate, endDate: endDate)
|
||||
```
|
||||
|
||||
To:
|
||||
```swift
|
||||
/// Filter games with full team and stadium data within date range
|
||||
func filterRichGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [RichGame] {
|
||||
let games = try await filterGames(sports: sports, startDate: startDate, endDate: endDate)
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Core/Services/DataProvider.swift
|
||||
git commit -m "refactor: rename fetchRichGames to filterRichGames"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Add DataProvider.allGames method
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Core/Services/DataProvider.swift` (add after filterGames, around line 145)
|
||||
|
||||
**Step 1: Add new method**
|
||||
|
||||
```swift
|
||||
/// Get all games for specified sports (no date filtering)
|
||||
func allGames(for sports: Set<Sport>) async throws -> [Game] {
|
||||
guard let context = modelContext else {
|
||||
throw DataProviderError.contextNotConfigured
|
||||
}
|
||||
|
||||
let sportStrings = sports.map { $0.rawValue }
|
||||
|
||||
let descriptor = FetchDescriptor<CanonicalGame>(
|
||||
predicate: #Predicate<CanonicalGame> { game in
|
||||
game.deprecatedAt == nil
|
||||
},
|
||||
sortBy: [SortDescriptor(\.dateTime)]
|
||||
)
|
||||
|
||||
let canonicalGames = try context.fetch(descriptor)
|
||||
|
||||
return canonicalGames.compactMap { canonical -> Game? in
|
||||
guard sportStrings.contains(canonical.sport) else { return nil }
|
||||
return canonical.toDomain()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Core/Services/DataProvider.swift
|
||||
git commit -m "feat: add allGames method for unfiltered game access"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 10: Add DataProvider.allRichGames method
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Core/Services/DataProvider.swift` (add after allGames)
|
||||
|
||||
**Step 1: Add new method**
|
||||
|
||||
```swift
|
||||
/// Get all games with full team and stadium data (no date filtering)
|
||||
func allRichGames(for sports: Set<Sport>) async throws -> [RichGame] {
|
||||
let games = try await allGames(for: sports)
|
||||
|
||||
return games.compactMap { game in
|
||||
guard let homeTeam = teamsById[game.homeTeamId],
|
||||
let awayTeam = teamsById[game.awayTeamId],
|
||||
let stadium = stadiumsById[game.stadiumId] else {
|
||||
return nil
|
||||
}
|
||||
return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Core/Services/DataProvider.swift
|
||||
git commit -m "feat: add allRichGames method for unfiltered rich game access"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 11: Update TripCreationViewModel.loadGamesForBrowsing
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Features/Trip/ViewModels/TripCreationViewModel.swift:491-497`
|
||||
|
||||
**Step 1: Remove 90-day limit, use allGames**
|
||||
|
||||
Change from:
|
||||
```swift
|
||||
// Fetch games for next 90 days for browsing
|
||||
let browseEndDate = Calendar.current.date(byAdding: .day, value: 90, to: Date()) ?? endDate
|
||||
games = try await dataProvider.fetchGames(
|
||||
sports: selectedSports,
|
||||
startDate: Date(),
|
||||
endDate: browseEndDate
|
||||
)
|
||||
```
|
||||
|
||||
To:
|
||||
```swift
|
||||
// Fetch ALL available games for browsing (no date restrictions)
|
||||
games = try await dataProvider.allGames(for: selectedSports)
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Features/Trip/ViewModels/TripCreationViewModel.swift
|
||||
git commit -m "feat: remove 90-day limit from game browsing, show all games"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 12: Update MockCloudKitService sync methods
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTimeTests/Mocks/MockCloudKitService.swift:165-206`
|
||||
|
||||
**Step 1: Update fetchStadiumsForSync**
|
||||
|
||||
```swift
|
||||
func fetchStadiumsForSync(since lastSync: Date?) async throws -> [CloudKitService.SyncStadium] {
|
||||
try await simulateNetwork()
|
||||
let filtered = lastSync == nil ? stadiums : stadiums // Mock doesn't track modificationDate
|
||||
return filtered.map { stadium in
|
||||
CloudKitService.SyncStadium(
|
||||
stadium: stadium,
|
||||
canonicalId: stadium.id
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Update fetchTeamsForSync**
|
||||
|
||||
```swift
|
||||
func fetchTeamsForSync(since lastSync: Date?) async throws -> [CloudKitService.SyncTeam] {
|
||||
try await simulateNetwork()
|
||||
return teams.map { team in
|
||||
CloudKitService.SyncTeam(
|
||||
team: team,
|
||||
canonicalId: team.id,
|
||||
stadiumCanonicalId: team.stadiumId
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Update fetchGamesForSync**
|
||||
|
||||
```swift
|
||||
func fetchGamesForSync(since lastSync: Date?) async throws -> [CloudKitService.SyncGame] {
|
||||
try await simulateNetwork()
|
||||
|
||||
return games.map { game in
|
||||
CloudKitService.SyncGame(
|
||||
game: game,
|
||||
canonicalId: game.id,
|
||||
homeTeamCanonicalId: game.homeTeamId,
|
||||
awayTeamCanonicalId: game.awayTeamId,
|
||||
stadiumCanonicalId: game.stadiumId
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTimeTests/Mocks/MockCloudKitService.swift
|
||||
git commit -m "test: update MockCloudKitService signatures for delta sync"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 13: Update MockAppDataProvider methods
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTimeTests/Mocks/MockAppDataProvider.swift:157-189`
|
||||
|
||||
**Step 1: Rename fetchGames to filterGames**
|
||||
|
||||
```swift
|
||||
func filterGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [Game] {
|
||||
fetchGamesCallCount += 1
|
||||
await simulateLatency()
|
||||
|
||||
if config.shouldFailOnFetch {
|
||||
throw DataProviderError.contextNotConfigured
|
||||
}
|
||||
|
||||
return games.filter { game in
|
||||
sports.contains(game.sport) &&
|
||||
game.dateTime >= startDate &&
|
||||
game.dateTime <= endDate
|
||||
}.sorted { $0.dateTime < $1.dateTime }
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Rename fetchRichGames to filterRichGames**
|
||||
|
||||
```swift
|
||||
func filterRichGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [RichGame] {
|
||||
fetchRichGamesCallCount += 1
|
||||
let filteredGames = try await filterGames(sports: sports, startDate: startDate, endDate: endDate)
|
||||
|
||||
return filteredGames.compactMap { game in
|
||||
richGame(from: game)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Add allGames method**
|
||||
|
||||
```swift
|
||||
func allGames(for sports: Set<Sport>) async throws -> [Game] {
|
||||
fetchGamesCallCount += 1
|
||||
await simulateLatency()
|
||||
|
||||
if config.shouldFailOnFetch {
|
||||
throw DataProviderError.contextNotConfigured
|
||||
}
|
||||
|
||||
return games.filter { game in
|
||||
sports.contains(game.sport)
|
||||
}.sorted { $0.dateTime < $1.dateTime }
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Add allRichGames method**
|
||||
|
||||
```swift
|
||||
func allRichGames(for sports: Set<Sport>) async throws -> [RichGame] {
|
||||
fetchRichGamesCallCount += 1
|
||||
let allGames = try await allGames(for: sports)
|
||||
|
||||
return allGames.compactMap { game in
|
||||
richGame(from: game)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTimeTests/Mocks/MockAppDataProvider.swift
|
||||
git commit -m "test: update MockAppDataProvider with renamed and new methods"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 14: Fix all callers of renamed methods
|
||||
|
||||
**Files:**
|
||||
- Search and update all files calling the old method names
|
||||
|
||||
**Step 1: Find all usages**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -r "fetchGames\|fetchRichGames" --include="*.swift" SportsTime/ SportsTimeTests/ | grep -v "Mock"
|
||||
```
|
||||
|
||||
**Step 2: Update each caller**
|
||||
|
||||
For each file found:
|
||||
- `fetchGames(` → `filterGames(` (when using date parameters)
|
||||
- `fetchRichGames(` → `filterRichGames(` (when using date parameters)
|
||||
|
||||
Common files likely to need updates:
|
||||
- `GameMatcher.swift`
|
||||
- `ScheduleMatcher.swift`
|
||||
- `TripPlanningEngine.swift`
|
||||
- Various test files
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "refactor: update all callers to use renamed filter methods"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 15: Run tests and fix any failures
|
||||
|
||||
**Step 1: Run full test suite**
|
||||
|
||||
```bash
|
||||
xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test
|
||||
```
|
||||
|
||||
**Step 2: Fix any compilation errors or test failures**
|
||||
|
||||
Most likely issues:
|
||||
- Missing method implementations
|
||||
- Signature mismatches between mock and real implementations
|
||||
- Tests expecting old method names
|
||||
|
||||
**Step 3: Commit fixes**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: resolve test failures from delta sync refactor"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 16: Final verification
|
||||
|
||||
**Step 1: Verify CloudKit sync logic**
|
||||
|
||||
Review the sync flow:
|
||||
1. First launch: `lastSync == nil` → fetches ALL records
|
||||
2. Subsequent syncs: `lastSync == Date` → fetches only modified records
|
||||
|
||||
**Step 2: Verify game browsing**
|
||||
|
||||
1. Build and run app
|
||||
2. Go to "By Games" mode
|
||||
3. Select MLB
|
||||
4. Verify Houston Astros shows full season (160+ games)
|
||||
|
||||
**Step 3: Final commit**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "feat: complete delta sync and unlimited game browsing implementation"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `CloudKitService.swift` | 3 methods updated to use `since: Date?` parameter |
|
||||
| `CanonicalSyncService.swift` | 3 methods updated to pass `lastSync` |
|
||||
| `DataProvider.swift` | 2 methods renamed, 2 methods added |
|
||||
| `TripCreationViewModel.swift` | 1 method updated to use `allGames` |
|
||||
| `MockCloudKitService.swift` | 3 methods updated to match new signatures |
|
||||
| `MockAppDataProvider.swift` | 2 methods renamed, 2 methods added |
|
||||
| Various callers | Updated to use new method names |
|
||||
Reference in New Issue
Block a user