commit 008ff511015134a2ac84de5d8cedb4a83381d276
parent 5474938b1f03bb7dbc52c77ed1996927b54324da
Author: Michael Camilleri <[email protected]>
Date: Fri, 29 May 2026 21:22:14 +0900
Group revoked games into their own list section
This commit pulls revoked games out of both groups into a new 'Revoked'
section rendered after 'In progress' in both the list and grid layouts.
the section autohides when empty, like the others.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Diffstat:
2 files changed, 45 insertions(+), 2 deletions(-)
diff --git a/Crossmate/Sync/CloudQuery.swift b/Crossmate/Sync/CloudQuery.swift
@@ -508,6 +508,19 @@ extension SyncEngine {
info.scope == scopeValue
else { return false }
+ // The zone has already been confirmed missing server-side (see
+ // `applyZoneOrphaning`). Re-querying it just fails with `.zoneNotFound`
+ // (CKError 26) every time the revoked puzzle appears, leaving the
+ // diagnostics `Last Error` stuck on "Zone does not exist". Report the
+ // freshen as handled so the caller skips the full-DB `fetchChanges`
+ // fallback too — there is nothing left to converge.
+ guard !info.isAccessRevoked else {
+ await trace(
+ "\(label) game catch-up: \(gameID.uuidString.prefix(8)) skipped (access revoked)"
+ )
+ return true
+ }
+
let checkpointKey = "\(scopeValue):\(gameID.uuidString)"
let since = liveQueryCheckpoints[checkpointKey]?
.addingTimeInterval(-liveQueryCheckpointOverlap)
diff --git a/Crossmate/Views/GameListView.swift b/Crossmate/Views/GameListView.swift
@@ -190,10 +190,13 @@ struct GameListView: View {
private func content(usesRoomierType: Bool) -> some View {
let summaries = games.compactMap { summaryCache.summary(for: $0) }
let inProgress = summaries
- .filter { $0.completedAt == nil }
+ .filter { $0.completedAt == nil && !$0.isAccessRevoked }
+ .sorted { ($0.updatedAt ?? .distantPast) > ($1.updatedAt ?? .distantPast) }
+ let revoked = summaries
+ .filter { $0.isAccessRevoked }
.sorted { ($0.updatedAt ?? .distantPast) > ($1.updatedAt ?? .distantPast) }
let completed = summaries
- .filter { $0.completedAt != nil }
+ .filter { $0.completedAt != nil && !$0.isAccessRevoked }
.sorted { ($0.completedAt ?? .distantPast) > ($1.completedAt ?? .distantPast) }
let visibleCount = min(completedVisibleCount, completed.count)
let visibleCompleted = Array(completed.prefix(visibleCount))
@@ -210,6 +213,7 @@ struct GameListView: View {
gridLayout(
invites: visibleInvites,
inProgress: inProgress,
+ revoked: revoked,
completed: visibleCompleted,
hasMore: hasMore,
usesRoomierType: usesRoomierType
@@ -218,6 +222,7 @@ struct GameListView: View {
listLayout(
invites: visibleInvites,
inProgress: inProgress,
+ revoked: revoked,
completed: visibleCompleted,
hasMore: hasMore,
usesRoomierType: usesRoomierType
@@ -246,6 +251,7 @@ struct GameListView: View {
private func listLayout(
invites: [InviteEntity],
inProgress: [GameSummary],
+ revoked: [GameSummary],
completed: [GameSummary],
hasMore: Bool,
usesRoomierType: Bool
@@ -271,6 +277,16 @@ struct GameListView: View {
}
}
+ if !revoked.isEmpty {
+ Section {
+ ForEach(revoked) { game in
+ rowView(for: game, usesRoomierType: usesRoomierType)
+ }
+ } header: {
+ Text("Revoked")
+ }
+ }
+
if !completed.isEmpty {
Section {
ForEach(completed) { game in
@@ -300,6 +316,7 @@ struct GameListView: View {
private func gridLayout(
invites: [InviteEntity],
inProgress: [GameSummary],
+ revoked: [GameSummary],
completed: [GameSummary],
hasMore: Bool,
usesRoomierType: Bool
@@ -332,6 +349,19 @@ struct GameListView: View {
}
}
+ if !revoked.isEmpty {
+ Section {
+ LazyVGrid(columns: gridColumns, spacing: 12) {
+ ForEach(revoked) { game in
+ gameCard(for: game, usesRoomierType: usesRoomierType)
+ }
+ }
+ .padding(.horizontal)
+ } header: {
+ gridSectionHeader("Revoked")
+ }
+ }
+
if !completed.isEmpty {
Section {
LazyVGrid(columns: gridColumns, spacing: 12) {