crossmate

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

commit 5a4d06bfa7b576d0f4e5185ad1698427ae785bdf
parent 2b69ddbe77e57913baef4ad2d2f4ed099399fa2d
Author: Michael Camilleri <[email protected]>
Date:   Sat,  6 Jun 2026 12:45:13 +0900

Add cache busting value for undo/redo

Diffstat:
MCrossmate/Persistence/GameMutator.swift | 9+++++++++
1 file changed, 9 insertions(+), 0 deletions(-)

diff --git a/Crossmate/Persistence/GameMutator.swift b/Crossmate/Persistence/GameMutator.swift @@ -20,6 +20,10 @@ final class GameMutator { private let authorIDProvider: (@MainActor () -> String?)? private let onLocalCellEdit: (@MainActor (RealtimeCellEdit) -> Void)? private let onLocalCellEditBatch: (@MainActor ([RealtimeCellEdit]) -> Void)? + /// Observation token for undo/redo availability. The journal itself is not + /// observable, so `canUndo`/`canRedo` read this value and journal mutations + /// bump it to invalidate SwiftUI menu state. + private var journalRevision = 0 /// While non-nil, `emitMove` parks its live broadcast here instead of /// firing `onLocalCellEdit` per cell. A bulk gesture (check/clear/undo of a @@ -166,11 +170,13 @@ final class GameMutator { /// is cheap (a derivation pass over the in-memory journal) and drives the /// enabled state of the undo/redo controls. var canUndo: Bool { + _ = journalRevision guard !isAccessRevoked, !isCompleted, let movesJournal else { return false } return movesJournal.canUndo(gameID: gameID) } var canRedo: Bool { + _ = journalRevision guard !isAccessRevoked, !isCompleted, let movesJournal else { return false } return movesJournal.canRedo(gameID: gameID) } @@ -190,6 +196,7 @@ final class GameMutator { while let plan = movesJournal.planUndo(gameID: gameID) { if applyRestores(plan.restores, kind: .undo) { return cursorTarget(for: plan) } movesJournal.markUndoConsumed(stepID: plan.stepID, gameID: gameID) + journalRevision += 1 } return nil } @@ -201,6 +208,7 @@ final class GameMutator { while let plan = movesJournal.planRedo(gameID: gameID) { if applyRestores(plan.restores, kind: .redo) { return cursorTarget(for: plan) } movesJournal.markRedoConsumed(stepID: plan.stepID, gameID: gameID) + journalRevision += 1 } return nil } @@ -320,6 +328,7 @@ final class GameMutator { batchID: batchID, direction: direction ) + journalRevision += 1 } guard let movesUpdater else { return }