06/23 Fix High-impact issues

This commit is contained in:
Nguyen Ngo
2026-06-23 17:32:19 -04:00
parent 32caf735e8
commit a1afea095d
4 changed files with 50 additions and 22 deletions
+26 -14
View File
@@ -268,6 +268,16 @@ class SyncManager: ObservableObject {
dup.uploadStatus = "uploaded"
}
// Pre-fetch parent records ONCE before the loop.
// Without this, every successful photo upload fetched ALL LocalInspection
// and ALL LocalIssue records from SwiftData to find the parent
// N photos 2N full-table fetches. Pre-fetching here reduces that
// to 2 fetches regardless of how many photos are in the queue.
// Fetch-all + filter in Swift #Predicate with a captured String variable
// causes "LocalInspection is ambiguous" under Xcode 26 (CLAUDE.md rule 3).
let allInspections = (try? context.fetch(FetchDescriptor<LocalInspection>())) ?? []
let allIssues = (try? context.fetch(FetchDescriptor<LocalIssue>())) ?? []
for photo in pending {
do {
let serverPath = try await APIClient.shared.uploadPhoto(
@@ -277,20 +287,18 @@ class SyncManager: ObservableObject {
photo.serverPath = serverPath
photo.uploadStatus = "uploaded"
// Update parent inspection form field value
// Update parent inspection form field value.
// Pre-fetched before the loop not repeated per photo.
if photo.entityType == "inspection", let fieldId = photo.fieldId {
let entityId = photo.entityLocalId
// Fetch-all + filter in Swift #Predicate with a captured String
// variable causes "LocalInspection is ambiguous" under Xcode 26
// SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor (CLAUDE.md rule 3).
let allInspections = (try? context.fetch(FetchDescriptor<LocalInspection>())) ?? []
allInspections.first(where: { $0.localId == entityId })?.setValue(serverPath, forFieldId: fieldId)
allInspections.first(where: { $0.localId == entityId })?
.setValue(serverPath, forFieldId: fieldId)
}
// Update parent issue photo paths array
// Update parent issue photo paths array.
// Pre-fetched before the loop not repeated per photo.
if photo.entityType == "issue" {
let entityId = photo.entityLocalId
let allIssues = (try? context.fetch(FetchDescriptor<LocalIssue>())) ?? []
if let issue = allIssues.first(where: { $0.localId == entityId }) {
var paths = issue.photoServerPaths
if !paths.contains(serverPath) { paths.append(serverPath) }
@@ -328,12 +336,12 @@ class SyncManager: ObservableObject {
inspection.status = "synced"
// Clear follow-up flag on parent.
if let parentLocalId = inspection.parentLocalId {
let allInspections = try? context.fetch(FetchDescriptor<LocalInspection>())
if let parent = allInspections?.first(where: { $0.localId == parentLocalId }) {
parent.followUpRequired = false
parent.followUpNote = nil
}
// Reuses the `all` array already fetched at the top of this
// function avoids a redundant full-table fetch per inspection.
if let parentLocalId = inspection.parentLocalId,
let parent = all.first(where: { $0.localId == parentLocalId }) {
parent.followUpRequired = false
parent.followUpNote = nil
}
try? context.save()
@@ -583,6 +591,10 @@ class SyncManager: ObservableObject {
}
func updatePendingCount(context: ModelContext) {
// Fetch-all + filter in Swift #Predicate with string literals is
// banned under Xcode 26 SWIFT_DEFAULT_ACTOR_ISOLATION (CLAUDE.md rule 3).
// These fetches are lightweight (no relationships loaded) and run once
// per sync cycle at the very end, not in a hot loop.
let inspCount = (try? context.fetch(FetchDescriptor<LocalInspection>()))?
.filter { $0.syncStatus == "pending" }.count ?? 0
let issueCount = (try? context.fetch(FetchDescriptor<LocalIssue>()))?