(data: Record<string, unknown>)
| 92 | // Merge from a realtime payload (no API call needed). |
| 93 | // Returns true if any data actually changed locally. |
| 94 | export function mergeFromRealtimePayload(data: Record<string, unknown>): boolean { |
| 95 | // Skip if this update was triggered by our own upload (within 5s window) |
| 96 | if (Date.now() - lastUploadAt < 5000) return false; |
| 97 | |
| 98 | let changed = false; |
| 99 | |
| 100 | const remoteCompleted = new Set<number>((data.completed as number[]) ?? []); |
| 101 | const localCompleted = loadCompleted(); |
| 102 | if (!setsEqual(localCompleted, remoteCompleted)) { |
| 103 | saveCompleted(remoteCompleted); |
| 104 | changed = true; |
| 105 | } |
| 106 | |
| 107 | const remoteStarred = new Set<number>((data.starred as number[]) ?? []); |
| 108 | const localStarred = loadStarred(); |
| 109 | if (!setsEqual(localStarred, remoteStarred)) { |
| 110 | saveStarred(remoteStarred); |
| 111 | changed = true; |
| 112 | } |
| 113 | |
| 114 | const remoteNotes = (data.notes as Record<number, string>) ?? {}; |
| 115 | const localNotes = loadNotes(); |
| 116 | if (!recordsEqual(localNotes, remoteNotes)) { |
| 117 | saveNotes(remoteNotes); |
| 118 | changed = true; |
| 119 | } |
| 120 | |
| 121 | const remoteDates = (data.solved_dates as Record<number, string>) ?? {}; |
| 122 | const localDates = loadSolvedDates(); |
| 123 | if (!recordsEqual(localDates, remoteDates)) { |
| 124 | saveSolvedDates(remoteDates); |
| 125 | changed = true; |
| 126 | } |
| 127 | |
| 128 | const remoteReminders = (data.reminders as Record<number, Reminder>) ?? {}; |
| 129 | const localReminders = loadReminders(); |
| 130 | if (JSON.stringify(localReminders) !== JSON.stringify(remoteReminders)) { |
| 131 | saveReminders(remoteReminders); |
| 132 | changed = true; |
| 133 | } |
| 134 | |
| 135 | return changed; |
| 136 | } |
| 137 | |
| 138 | function setsEqual(a: Set<number>, b: Set<number>): boolean { |
| 139 | if (a.size !== b.size) return false; |
no test coverage detected