# CloudKit Sync Documentation

Complete reference for USB Meter's multi-device synchronization system using CloudKit.

---

## Quick Start

**Problem:** How do settings sync across my iPhone, iPad, and Mac?

**Answer:**
1. When you connect a meter on iPhone → `Meter.swift` calls `appData.publishMeterConnection()`
2. This updates a **DeviceSettings** record in Core Data
3. `NSPersistentCloudKitContainer` automatically syncs it to iCloud
4. Your iPad + Mac receive a notification → refresh UI
5. Within 2-5 seconds, all devices show "Connected on iPhone"

---

## Documentation Files

### 1. **SCHEMA.md** — Data Model & Structure
- DeviceSettings entity attributes
- CloudKit record mapping
- Why `uniquenessConstraint` was removed
- Version history (v1 → v2 → v3)

**Read this if:** You need to understand the data structure, add fields, or debug data corruption.

### 2. **MECHANISM.md** — How Sync Works
- Complete architecture diagram
- Step-by-step local write flow
- Remote change reception flow
- Core AppData methods explained
- Connection lifecycle (TTL, expiry, locking)
- Error handling & recovery

**Read this if:** You're troubleshooting sync issues, understanding threading, or extending functionality.

### 3. **TROUBLESHOOTING.md** — Debugging Guide
- Configuration verification checklist
- Common issues & fixes
- Real-time monitoring code
- Performance benchmarks
- Log analysis

**Read this if:** Sync isn't working, you want to add debug UI, or need production troubleshooting.

---

## Key Concepts

### DeviceSettings Record
Represents a single Bluetooth meter device with its metadata:

```
macAddress: "aa:bb:cc:dd:ee:ff"  ← identifies the meter
meterName: "Kitchen Meter"        ← user-friendly name
modelType: "TC66C"                ← hardware type
connectedByDeviceID: "UUID"       ← which device is using it now
connectedExpiryAt: Date           ← connection lock TTL (120s)
updatedAt: Date                   ← last sync timestamp
```

### Sync Heartbeat
- **Connect:** `publishMeterConnection()` → saves record → syncs
- **Disconnect:** `clearMeterConnection()` → clears record → syncs
- **Refresh:** Periodically calls `reloadSettingsFromCloudStore()` to check for remote changes

### Conflict Resolution
If Device A and B edit the same record simultaneously:
- **Policy:** `NSMergeByPropertyStoreTrumpMergePolicy`
- **Rule:** CloudKit's version always wins
- **Edge case:** User might see their local change revert (~1s after)

---

## Architecture Summary

```
Meter UI ──→ BT Connection ──→ AppData ──→ CloudDeviceSettingsStore ──→ Core Data
                                  ↓                ↓
                          [CloudKit Sync]   [persisted locally]
                                  ↓
                          [iCloud Servers]
                                  ↓
                          Other Devices ──→ UI Updates
```

---

## Critical Files in Codebase

| File | Role |
|------|------|
| `AppDelegate.swift` | Creates `NSPersistentCloudKitContainer` (line 85-127) |
| `SceneDelegate.swift` | Calls `activateCloudDeviceSync()` at startup |
| `AppData.swift` | Orchestrates sync: `publishMeterConnection`, `clearMeterConnection`, `reloadSettingsFromCloudStore` |
| `Meter.swift` | Line 109, 118, 132: Calls `appData` methods on state changes |
| `CKModel.xcdatamodeld/USB_Meter 2.xcdatamodel/contents` | Core Data schema (no uniqueness constraint) |
| `USB Meter.entitlements` | iCloud container + CloudKit permissions |

---

## Debugging Checklist

- [ ] CloudKit account status = `.available`?
- [ ] Entitlements file includes `iCloud.ro.xdev.USB-Meter`?
- [ ] Model version is `USB_Meter 2.xcdatamodel` or later?
- [ ] `cloudStoreRebuildVersion` ≥ 3?
- [ ] Remote change observer registered in `setupRemoteChangeNotificationObserver()`?
- [ ] Merge policy = `NSMergeByPropertyStoreTrumpMergePolicy`?

---

## Common Workflows

### "Settings won't sync to my other device"
1. Check CloudKit account (TROUBLESHOOTING.md § 1)
2. Verify entitlements (TROUBLESHOOTING.md § 2)
3. Force reload: `appData.reloadSettingsFromCloudStore()`
4. Check logs for `NSPersistentStoreRemoteChange` notifications

### "I see duplicate meter entries"
1. Verify `cloudStoreRebuildVersion = 3` deployed
2. Check `rebuildCanonicalStoreIfNeeded` ran (UserDefaults `cloudStoreRebuildVersion.3`)
3. If duplicates persist, see TROUBLESHOOTING.md § Issue 2

### "Connection lock doesn't expire"
1. Check system clock on Device A (ParentalControls?)
2. Manual unlock: `appData.clearMeterConnection(mac: "...")`
3. Verify TTL is 120s in `setConnection()`

### "I need to add a new sync field"
1. Edit `USB_Meter 2.xcdatamodel/contents` → add attribute (optional!)
2. Bump Core Data version if needed (lightweight migration)
3. Update `CloudDeviceSettingsStore` to populate it
4. Sync resumes automatically

---

## Performance

- **Local write latency:** ~10ms
- **CloudKit network push:** ~1-2s (typical)
- **Remote notification delivery:** ~2-5s
- **UI update after notification:** <100ms
- **Full reload (10 records):** ~50ms

Total end-to-end for a setting change: **~5-8 seconds** (typical, good network)

---

## Testing Tips

Use two devices on same iCloud account:

1. **Connect meter on iPhone** → Check iPad within 5s for "Connected on iPhone"
2. **Rename meter on iPhone** → Name updates on iPad instantly (local) after sync
3. **Airplane mode iPhone** → Changes queue locally
4. **Reconnect iPhone** → Changes resync automatically
5. **Switch iCloud accounts** → No data loss (encrypted in CloudKit)

---

## References

- [NSPersistentCloudKitContainer docs](https://developer.apple.com/documentation/coredata/nspersistentcloudkitcontainer)
- [CloudKit best practices](https://developer.apple.com/documentation/cloudkit/best_practices)
- [Core Data concurrency](https://developer.apple.com/documentation/coredata/concurrency)
- [Merging edited Core Data objects](https://developer.apple.com/documentation/coredata/nsmergepolicy)

---

## Questions?

Refer to specific sections above by file, or check the troubleshooting checklist in TROUBLESHOOTING.md.
