commit c563c29c90d683cc79fe8b6beca73d02e7447c6b
parent b421eb5c1e8dbbde7790dee6030028a608a65c87
Author: Michael Camilleri <[email protected]>
Date: Tue, 28 Apr 2026 11:49:10 +0900
Fix puzzle timestamp updating
When puzzles were updated, this was not always being reflected in the
game list. This commit fixes that.
Co-Authored-By: Codex GPT 5.5 <[email protected]>
Diffstat:
3 files changed, 35 insertions(+), 0 deletions(-)
diff --git a/Crossmate/Sync/MoveBuffer.swift b/Crossmate/Sync/MoveBuffer.swift
@@ -157,6 +157,9 @@ actor MoveBuffer {
let lamport = game.lamportHighWater + 1
game.lamportHighWater = lamport
+ if game.updatedAt.map({ $0 < pending.enqueuedAt }) ?? true {
+ game.updatedAt = pending.enqueuedAt
+ }
let entity = MoveEntity(context: context)
entity.game = game
diff --git a/Crossmate/Sync/SyncEngine.swift b/Crossmate/Sync/SyncEngine.swift
@@ -471,6 +471,10 @@ actor SyncEngine {
if let game = entity.game, move.lamport > game.lamportHighWater {
game.lamportHighWater = move.lamport
}
+ if let game = entity.game,
+ game.updatedAt.map({ $0 < move.createdAt }) ?? true {
+ game.updatedAt = move.createdAt
+ }
}
private nonisolated func applyPlayerRecord(
diff --git a/Tests/Unit/MoveBufferTests.swift b/Tests/Unit/MoveBufferTests.swift
@@ -105,6 +105,24 @@ struct MoveBufferTests {
#expect(highWater == 11)
}
+ @Test("Flushed moves bump the parent game's updatedAt timestamp")
+ func flushedMovesUpdateGameTimestamp() async throws {
+ let (persistence, gameID) = try makePersistenceWithGame()
+ let before = try #require(fetchUpdatedAt(gameID: gameID, persistence: persistence))
+ let buffer = MoveBuffer(
+ debounceInterval: .seconds(10),
+ persistence: persistence,
+ sink: { _ in }
+ )
+
+ try await Task.sleep(for: .milliseconds(10))
+ await buffer.enqueue(gameID: gameID, row: 0, col: 0, letter: "X", markKind: 0, checkedWrong: false, authorID: nil)
+ await buffer.flush()
+
+ let after = try #require(fetchUpdatedAt(gameID: gameID, persistence: persistence))
+ #expect(after > before)
+ }
+
@Test("Debounce coalesces rapid same-cell enqueues into one flush")
func debounceCoalescesRapidEnqueues() async throws {
let (persistence, gameID) = try makePersistenceWithGame()
@@ -206,6 +224,16 @@ struct MoveBufferTests {
}
}
+ private func fetchUpdatedAt(gameID: UUID, persistence: PersistenceController) -> Date? {
+ let context = persistence.container.newBackgroundContext()
+ return context.performAndWait {
+ let request = NSFetchRequest<GameEntity>(entityName: "GameEntity")
+ request.predicate = NSPredicate(format: "id == %@", gameID as CVarArg)
+ request.fetchLimit = 1
+ return try? context.fetch(request).first?.updatedAt
+ }
+ }
+
/// Extracts `MoveEntity` field values inside the background context so
/// no `NSManagedObject` escapes its owning context.
struct MoveValues {