crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

commit 93b8097d8ec51d9b38bac1cb8b3998285fe6e7f3
parent ee646a58690d30804aee98782ec632e1f85e721c
Author: Michael Camilleri <[email protected]>
Date:   Tue, 14 Apr 2026 23:03:23 +0900

Add telemetry for iCloud debugging

Diffstat:
MCrossmate/Sync/SyncEngine.swift | 28++++++++++++++++++++++++++--
1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/Crossmate/Sync/SyncEngine.swift b/Crossmate/Sync/SyncEngine.swift @@ -61,6 +61,14 @@ actor SyncEngine { let userRecordID = try await container.userRecordID() zoneID = CKRecordZone.ID(zoneName: zoneID.zoneName, ownerName: userRecordID.recordName) ownerResolved = true + await trace("resolveOwner: zoneID now \(zoneID.zoneName)/\(shortOwner(zoneID.ownerName))") + } + + private nonisolated func shortOwner(_ name: String) -> String { + // Shorten long owner record IDs so diagnostics stay readable. + // Keeps the last 8 characters so both devices can be compared visually. + guard name.count > 10 else { return name } + return "…" + String(name.suffix(8)) } // MARK: - Bootstrap @@ -76,7 +84,11 @@ actor SyncEngine { SyncStateEntity.current(in: context).zoneCreated } - guard !alreadyCreated else { return } + guard !alreadyCreated else { + await trace("bootstrap: zone already created, skipping") + return + } + await trace("bootstrap: creating zone \(zoneID.zoneName)") let zone = RecordSerializer.zone() let operation = CKModifyRecordZonesOperation( @@ -346,11 +358,18 @@ actor SyncEngine { } let changedZoneIDs = try await fetchDatabaseChanges(token: databaseToken, context: context) + let zoneDescriptions = changedZoneIDs + .map { "\($0.zoneName)/\(shortOwner($0.ownerName))" } + .joined(separator: ", ") + await trace("fetch: changedZoneIDs count=\(changedZoneIDs.count) [\(zoneDescriptions)]") // Only proceed if our zone has changes. Equality works here because // `resolveOwnerIfNeeded()` has rebuilt `zoneID` with the real owner // record ID, matching what the server returns. - guard changedZoneIDs.contains(zoneID) else { return } + guard changedZoneIDs.contains(zoneID) else { + await trace("fetch: our zone \(zoneID.zoneName)/\(shortOwner(zoneID.ownerName)) not in changed set, skipping zone fetch") + return + } // Step 2: Fetch zone-level changes let zoneToken: CKServerChangeToken? = context.performAndWait { @@ -358,6 +377,10 @@ actor SyncEngine { } let incomingRecords = try await fetchZoneChanges(token: zoneToken, context: context) + await trace("fetch: incomingRecords count=\(incomingRecords.count)") + for record in incomingRecords { + await trace("fetch: record \(record.recordType) name=\(record.recordID.recordName)") + } guard !incomingRecords.isEmpty else { return } @@ -369,6 +392,7 @@ actor SyncEngine { self.applyIncomingRecords(incomingRecords, in: context) try? context.save() } + await trace("fetch: applied \(incomingRecords.count) record(s) to Core Data") // Step 4: Route cell changes through the single inbox if let onRemoteCellChanges, !cellChanges.isEmpty {