commit 63e3eeea4d388dc75141d45cb66e11bbd6e5970c
parent e0bee2c7973d378c75d3331d8dabbcee33d05029
Author: Michael Camilleri <[email protected]>
Date: Fri, 12 Jun 2026 16:59:13 +0900
Use nicknames in announcement banners
Diffstat:
3 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/Crossmate/Persistence/GameStore.swift b/Crossmate/Persistence/GameStore.swift
@@ -1560,6 +1560,23 @@ final class GameStore {
return fetchPlayerEntity(gameID: gameID, authorID: authorID)?.name ?? ""
}
+ /// The user's private nickname for `authorID` (`FriendEntity.nickname`),
+ /// or `nil` when none is set. The same override `resolvedDisplayName`
+ /// applies, exposed here for surfaces hydrated through `GameStore`
+ /// rather than from a `FriendEntity` row — currently the catch-up
+ /// banner's `SessionMonitor.movesSummaries`.
+ func friendNickname(for authorID: String) -> String? {
+ guard !authorID.isEmpty else { return nil }
+ let request = NSFetchRequest<FriendEntity>(entityName: "FriendEntity")
+ request.predicate = NSPredicate(format: "authorID == %@", authorID)
+ request.fetchLimit = 1
+ guard let nickname = (try? context.fetch(request).first)?.nickname?
+ .trimmingCharacters(in: .whitespacesAndNewlines),
+ !nickname.isEmpty
+ else { return nil }
+ return nickname
+ }
+
/// The read cursor persisted for `(gameID, authorID)`, or `nil` when no row
/// exists or none has been stamped. Used by the local pause-diagnostics
/// mirror to compare this device's actual cursor against the value a peer's
diff --git a/Crossmate/Sync/SessionMonitor.swift b/Crossmate/Sync/SessionMonitor.swift
@@ -103,7 +103,10 @@ final class SessionMonitor {
summaries.append(SessionSummary(
gameID: gameID,
authorID: authorID,
- playerName: store.playerName(for: gameID, by: authorID),
+ // A nickname the user assigned via Rename wins over the
+ // peer's own published name, matching every other surface.
+ playerName: store.friendNickname(for: authorID)
+ ?? store.playerName(for: gameID, by: authorID),
puzzleTitle: puzzleTitle,
added: added,
cleared: cleared,
diff --git a/Tests/Unit/Sync/SessionMonitorTests.swift b/Tests/Unit/Sync/SessionMonitorTests.swift
@@ -147,6 +147,30 @@ struct SessionMonitorTests {
#expect(summary.isFirstObservation)
}
+ @Test("A friend nickname overrides the peer's published name in summaries")
+ func nicknameOverridesPlayerName() throws {
+ let fixture = try makeFixture()
+ try addPlayer(in: fixture, authorID: Self.alice, name: "Alice")
+ let ctx = fixture.persistence.viewContext
+ let friend = FriendEntity(context: ctx)
+ friend.authorID = Self.alice
+ friend.pairKey = "pair-alice"
+ friend.friendZoneName = "friend-pair-alice"
+ friend.friendZoneOwnerName = "_owner"
+ friend.databaseScope = 0
+ friend.createdAt = Date()
+ friend.nickname = "Mum"
+ try ctx.save()
+ try writeMoves(
+ in: fixture,
+ authorID: Self.alice,
+ cells: [position(0, 0): ("A", Date())]
+ )
+
+ let summary = try #require(fixture.monitor.movesSummaries(for: fixture.gameID).first)
+ #expect(summary.playerName == "Mum")
+ }
+
@Test("First observation skips a peer whose filled count is zero")
func firstObservationSkipsEmptyPeer() throws {
let fixture = try makeFixture()