crossmate

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

commit ead9dbcfe6399be76913e3da4b48a73ee3fa094d
parent 989419b0d641c27fc1194b68b70feac6f74b07ce
Author: Michael Camilleri <[email protected]>
Date:   Wed, 15 Apr 2026 02:19:40 +0900

Ensure game creation triggers iCloud sync

Diffstat:
MCrossmate/CrossmateApp.swift | 16+++++++++-------
MCrossmate/Persistence/GameStore.swift | 24++++++++++++++++++++++--
2 files changed, 31 insertions(+), 9 deletions(-)

diff --git a/Crossmate/CrossmateApp.swift b/Crossmate/CrossmateApp.swift @@ -104,6 +104,15 @@ struct RootView: View { nytAuth.loadStoredSession() ubiquityMonitor.start() + // Wire local outbox changes → sync engine push + store.onPendingChangesAvailable = { [syncEngine, syncMonitor] in + Task { + await Self.run("local pending push", monitor: syncMonitor) { + try await syncEngine.pushChanges() + } + } + } + // Wire app delegate → sync engine fetch appDelegate.onRemoteNotification = { [syncEngine, syncMonitor] in await Self.run("remote-notification fetch", monitor: syncMonitor) { @@ -222,13 +231,6 @@ private struct GameDestinationView: View { do { let (game, mutator) = try store.loadGame(id: gameID) let playerSession = PlayerSession(game: game, mutator: mutator) - mutator.onLocalMutation = { [syncEngine, syncMonitor] in - Task { - await RootView.run("local mutation push", monitor: syncMonitor) { - try await syncEngine.pushChanges() - } - } - } self._session = State(initialValue: playerSession) } catch { self._loadError = State(initialValue: String(describing: error)) diff --git a/Crossmate/Persistence/GameStore.swift b/Crossmate/Persistence/GameStore.swift @@ -78,6 +78,12 @@ final class GameStore { private(set) var currentMutator: GameMutator? private(set) var currentEntity: GameEntity? + /// Called after local work has been enqueued in the pending-change outbox. + /// The app layer wires this to the sync engine so persistence stays + /// independent of CloudKit scheduling. + @ObservationIgnored + var onPendingChangesAvailable: (() -> Void)? + init(persistence: PersistenceController) { self.persistence = persistence repairSyncedGamesIfNeeded() @@ -109,7 +115,7 @@ final class GameStore { let game = Game(puzzle: puzzle) restore(game: game, from: entity) - let mutator = GameMutator(game: game, gameEntity: entity, context: context) + let mutator = makeMutator(game: game, entity: entity) currentGame = game currentMutator = mutator @@ -162,6 +168,7 @@ final class GameStore { RecordSerializer.enqueueGamePending(for: entity, in: context) try context.save() + notifyPendingChangesAvailable() return gameID } @@ -227,7 +234,7 @@ final class GameStore { let game = Game(puzzle: puzzle) restore(game: game, from: entity) - let mutator = GameMutator(game: game, gameEntity: entity, context: context) + let mutator = makeMutator(game: game, entity: entity) currentGame = game currentMutator = mutator @@ -279,6 +286,7 @@ final class GameStore { RecordSerializer.enqueueGamePending(for: entity, in: context) try context.save() + notifyPendingChangesAvailable() return (entity, puzzle) } @@ -345,6 +353,18 @@ final class GameStore { } } + private func makeMutator(game: Game, entity: GameEntity) -> GameMutator { + let mutator = GameMutator(game: game, gameEntity: entity, context: context) + mutator.onLocalMutation = { [weak self] in + self?.notifyPendingChangesAvailable() + } + return mutator + } + + private func notifyPendingChangesAvailable() { + onPendingChangesAvailable?() + } + // MARK: - Remote changes /// Routes incoming remote cell changes through the current `GameMutator`,