crossmate

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

commit c4534b5531f266f8db128efdde3006301629c0fd
parent 389d8cd6a538abda8495edf14b89d8910cbbaaff
Author: Michael Camilleri <[email protected]>
Date:   Fri, 17 Apr 2026 14:52:19 +0900

Inject sync services via environment

Prior to this commit, GameListView and SettingsView held syncEngine and
syncMonitor only to forward them down to SyncDiagnosticsView, which was
the sole consumer.  This commit removes the pass-through properties and
inject both at the app root instead: SyncMonitor as an @Observable
environment value, SyncEngine via an @Entry EnvironmentValues key
(matching the existing nytPuzzleFetcher pattern, since actors can't be
@Observable).

Co-Authored-By: Claude Opus 4.7 <[email protected]>

Diffstat:
MCrossmate/CrossmateApp.swift | 4++--
MCrossmate/Sync/SyncEngine.swift | 5+++++
MCrossmate/Views/GameListView.swift | 4+---
MCrossmate/Views/SettingsView.swift | 5+----
MCrossmate/Views/SyncDiagnosticsView.swift | 9++++++---
5 files changed, 15 insertions(+), 12 deletions(-)

diff --git a/Crossmate/CrossmateApp.swift b/Crossmate/CrossmateApp.swift @@ -19,7 +19,9 @@ struct CrossmateApp: App { .environment(\.managedObjectContext, services.persistence.viewContext) .environment(services.nytAuth) .environment(services.ubiquityMonitor) + .environment(services.syncMonitor) .environment(\.nytPuzzleFetcher, services.nytFetcher) + .environment(\.syncEngine, services.syncEngine) } } } @@ -60,8 +62,6 @@ struct RootView: View { NavigationStack(path: $navigationPath) { GameListView( store: services.store, - syncEngine: services.syncEngine, - syncMonitor: services.syncMonitor, navigationPath: $navigationPath ) .navigationDestination(for: UUID.self) { gameID in diff --git a/Crossmate/Sync/SyncEngine.swift b/Crossmate/Sync/SyncEngine.swift @@ -1,6 +1,11 @@ import CloudKit import CoreData import Foundation +import SwiftUI + +extension EnvironmentValues { + @Entry var syncEngine: SyncEngine? = nil +} /// Owns the CloudKit container, custom zone, and sync lifecycle. All CloudKit /// operations run on this actor's serial executor, keeping token reads and diff --git a/Crossmate/Views/GameListView.swift b/Crossmate/Views/GameListView.swift @@ -3,8 +3,6 @@ import SwiftUI struct GameListView: View { let store: GameStore - let syncEngine: SyncEngine - let syncMonitor: SyncMonitor @Binding var navigationPath: NavigationPath @Environment(\.managedObjectContext) private var viewContext @@ -112,7 +110,7 @@ struct GameListView: View { } } .sheet(isPresented: $showingSettings) { - SettingsView(syncEngine: syncEngine, syncMonitor: syncMonitor) + SettingsView() } .sheet(isPresented: $showingNewGame) { NewGameSheet(store: store) diff --git a/Crossmate/Views/SettingsView.swift b/Crossmate/Views/SettingsView.swift @@ -1,9 +1,6 @@ import SwiftUI struct SettingsView: View { - let syncEngine: SyncEngine - let syncMonitor: SyncMonitor - @Environment(NYTAuthService.self) private var nytAuth @Environment(\.dismiss) private var dismiss @@ -24,7 +21,7 @@ struct SettingsView: View { Section("iCloud Sync") { NavigationLink("Diagnostics") { - SyncDiagnosticsView(syncEngine: syncEngine, syncMonitor: syncMonitor) + SyncDiagnosticsView() } } } diff --git a/Crossmate/Views/SyncDiagnosticsView.swift b/Crossmate/Views/SyncDiagnosticsView.swift @@ -2,8 +2,8 @@ import CloudKit import SwiftUI struct SyncDiagnosticsView: View { - let syncEngine: SyncEngine - let syncMonitor: SyncMonitor + @Environment(\.syncEngine) private var syncEngine + @Environment(SyncMonitor.self) private var syncMonitor @State private var isSyncing = false @@ -93,6 +93,7 @@ struct SyncDiagnosticsView: View { } } .task { + guard let syncEngine else { return } let snapshot = await syncEngine.diagnosticSnapshot() syncMonitor.updateSnapshot(snapshot) } @@ -101,6 +102,7 @@ struct SyncDiagnosticsView: View { // MARK: - Actions private func resetSyncState() async { + guard let syncEngine else { return } await syncEngine.resetSyncState() syncMonitor.note("Sync state reset (zone/subscription flags and tokens cleared)") let snapshot = await syncEngine.diagnosticSnapshot() @@ -108,6 +110,7 @@ struct SyncDiagnosticsView: View { } private func probeContainer() async { + guard let syncEngine else { return } syncMonitor.note("Starting container probe") let results = await syncEngine.probeContainer() for (name, result) in results { @@ -117,7 +120,7 @@ struct SyncDiagnosticsView: View { } private func runFullSync() async { - guard !isSyncing else { return } + guard !isSyncing, let syncEngine else { return } isSyncing = true defer { isSyncing = false }