crossmate

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

commit 6ea7d94a8b610fa65fa5bf946d42af87e26046f9
parent e9ddbc55ba936ba15acfdec7c6fb93735258d0e3
Author: Michael Camilleri <[email protected]>
Date:   Wed, 20 May 2026 11:09:07 +0900

Fetch readAt field in direct sync paths

Direct Player fetches used by live puzzle refresh, background session scans,
and zone discovery requested the cursor/name fields but omitted readAt. That
let the app spend CloudKit work applying Player records while missing the
sibling-device read cursor that clears unread badges.

This commit includes readAt in the direct query key lists, and immediately
enqueues the local Player record when watched inbound moves advance
lastReadOtherMoveAt for the currently visible puzzle. This keeps the existing
Player traffic model, but makes the read cursor converge promptly across the
user's devices.

Co-Authored-By: Codex GPT 5.5 <[email protected]>

Diffstat:
MCrossmate/Services/AppServices.swift | 7++++++-
MCrossmate/Sync/SyncEngine.swift | 6+++---
2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/Crossmate/Services/AppServices.swift b/Crossmate/Services/AppServices.swift @@ -229,7 +229,7 @@ final class AppServices { identity.currentID } - await syncEngine.setOnRemoteMovesUpdated { [store, identity] gameIDs in + await syncEngine.setOnRemoteMovesUpdated { [store, identity, syncEngine] gameIDs in store.noteIncomingMovesUpdate( gameIDs: gameIDs, currentAuthorID: identity.currentID @@ -237,6 +237,11 @@ final class AppServices { if let currentID = store.currentEntity?.id, gameIDs.contains(currentID) { store.refreshCurrentGame() + if NotificationState.isSuppressed(gameID: currentID), + let authorID = identity.currentID, + !authorID.isEmpty { + await syncEngine.enqueuePlayerRecord(gameID: currentID, authorID: authorID) + } } } diff --git a/Crossmate/Sync/SyncEngine.swift b/Crossmate/Sync/SyncEngine.swift @@ -743,7 +743,7 @@ actor SyncEngine { database: database, zoneID: info.zoneID, since: since, - desiredKeys: ["authorID", "name", "updatedAt", "selRow", "selCol", "selDir"] + desiredKeys: ["authorID", "name", "updatedAt", "selRow", "selCol", "selDir", "readAt"] ) let gameResults = try await gameResultsTask let moves = try await movesTask @@ -978,7 +978,7 @@ actor SyncEngine { database: database, zoneID: zone.zoneID, since: since, - desiredKeys: ["authorID", "name", "updatedAt", "selRow", "selCol", "selDir"] + desiredKeys: ["authorID", "name", "updatedAt", "selRow", "selCol", "selDir", "readAt"] ) let (pings, players) = try await (pingRecords, playerRecords) let activities = players.compactMap { record in @@ -1144,7 +1144,7 @@ actor SyncEngine { database: database, zoneID: zoneID, since: nil, - desiredKeys: ["authorID", "name", "updatedAt", "selRow", "selCol", "selDir"] + desiredKeys: ["authorID", "name", "updatedAt", "selRow", "selCol", "selDir", "readAt"] ) let (g, m, p) = try await (games, moves, players) guard !g.isEmpty else {