commit 49922995fe4e7d368a03b5f66d9a67d8cf8f66b7
parent 347d9f44d6b36f38766b16a0ecee15baa618d2b7
Author: Michael Camilleri <[email protected]>
Date: Thu, 21 May 2026 11:51:28 +0900
Prevent solved puzzle opens from sending win pings
This commit separates local completion events from observed synced completion
state in the Puzzle View. A solve reported by PlayerSession can still call
onComplete and fan out the .win ping, but loading or observing an
already-solved shared puzzle only updates the solved UI state. This prevents a
collaborator who merely opens a solved puzzle from being reported as the
solver.
Co-Authored-By: Codex GPT 5.5 <[email protected]>
Diffstat:
1 file changed, 33 insertions(+), 20 deletions(-)
diff --git a/Crossmate/Views/PuzzleView.swift b/Crossmate/Views/PuzzleView.swift
@@ -95,7 +95,8 @@ struct PuzzleView: View {
session: session,
roster: roster,
hasSolved: $hasSolved,
- onCompletionStateChanged: handleCompletionState
+ onLocalCompletionStateChanged: handleLocalCompletionState,
+ onObservedCompletionStateChanged: handleObservedCompletionState
))
.modifier(PuzzlePresentationModifier(
session: session,
@@ -211,23 +212,34 @@ struct PuzzleView: View {
}
}
- private func handleCompletionState(_ newValue: Game.CompletionState) {
- switch newValue {
- case .incomplete:
- break
- case .filledWithErrors:
- showErrorsAlert = true
- case .solved:
- guard !hasSolved else { return }
- hasSolved = true
- if session.isPencilMode {
- session.togglePencil()
- }
- Task { @MainActor in
- onComplete?()
- }
+ private func handleLocalCompletionState(_ newValue: Game.CompletionState) {
+ switch newValue {
+ case .incomplete:
+ break
+ case .filledWithErrors:
+ showErrorsAlert = true
+ case .solved:
+ guard !hasSolved else { return }
+ hasSolved = true
+ if session.isPencilMode {
+ session.togglePencil()
+ }
+ Task { @MainActor in
+ onComplete?()
}
}
+ }
+
+ private func handleObservedCompletionState(_ newValue: Game.CompletionState) {
+ switch newValue {
+ case .incomplete:
+ break
+ case .filledWithErrors:
+ showErrorsAlert = true
+ case .solved:
+ hasSolved = true
+ }
+ }
private var puzzleArea: some View {
ZStack {
@@ -910,7 +922,8 @@ private struct PuzzleLifecycleModifier: ViewModifier {
let session: PlayerSession
let roster: PlayerRoster
@Binding var hasSolved: Bool
- let onCompletionStateChanged: (Game.CompletionState) -> Void
+ let onLocalCompletionStateChanged: (Game.CompletionState) -> Void
+ let onObservedCompletionStateChanged: (Game.CompletionState) -> Void
func body(content: Content) -> some View {
content
@@ -919,15 +932,15 @@ private struct PuzzleLifecycleModifier: ViewModifier {
}
.onAppear {
if session.game.completionState == .solved {
- onCompletionStateChanged(.solved)
+ hasSolved = true
}
}
.onChange(of: session.game.completionState) { _, newValue in
- onCompletionStateChanged(newValue)
+ onObservedCompletionStateChanged(newValue)
}
.onAppear {
session.onCompletionStateChanged = { newValue in
- onCompletionStateChanged(newValue)
+ onLocalCompletionStateChanged(newValue)
}
}
.onDisappear {