// // SessionRecord.swift // Keging // // 완주한 세트 기록 — 중단한 세트는 저장하지 않는다 (CLAUDE.md §3.1) // import Foundation import Combine nonisolated struct SessionRecord: Codable, Identifiable, Equatable { var id: UUID = UUID() var startedAt: Date var reps: Int var contractSeconds: Int var relaxSeconds: Int } @MainActor final class RecordStore: ObservableObject { static let shared = RecordStore() @Published private(set) var records: [SessionRecord] = [] private var fileURL: URL { AppGroup.containerURL.appendingPathComponent("records.json") } private init() { load() } func load() { guard let data = try? Data(contentsOf: fileURL), let saved = try? JSONDecoder().decode([SessionRecord].self, from: data) else { return } records = saved.sorted { $0.startedAt < $1.startedAt } } /// 중복(id) 병합 안전 — 워치에서 넘어온 기록도 이 경로로 합류 func append(_ record: SessionRecord) { guard !records.contains(where: { $0.id == record.id }) else { return } records.append(record) records.sort { $0.startedAt < $1.startedAt } save() } private func save() { if let data = try? JSONEncoder().encode(records) { try? data.write(to: fileURL, options: .atomic) } } #if DEBUG /// 검증 시드 전용 — 전체 교체 func _replaceAll(_ newRecords: [SessionRecord]) { records = newRecords.sorted { $0.startedAt < $1.startedAt } save() } #endif }