@glance-apps/sync 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/package.json +1 -1
- package/src/merge.js +20 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.3.0] - 2026-06-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Multi-user roster sync in `mergeSyncData`.** A `users` array is now merged
|
|
15
|
+
across devices using last-write-wins per user, keyed by `syncId` (falling back
|
|
16
|
+
to `id`) and resolved by `updatedAt`. A roster signature (id, `updatedAt`,
|
|
17
|
+
`deleted` flag, and `name`) drives the `localChanged` / `remoteChanged` dirty
|
|
18
|
+
flags so roster edits propagate. The per-device `multiUserEnabled` toggle is
|
|
19
|
+
intentionally not merged.
|
|
20
|
+
|
|
10
21
|
## [1.2.1] - 2026-06-14
|
|
11
22
|
|
|
12
23
|
### Fixed
|
package/package.json
CHANGED
package/src/merge.js
CHANGED
|
@@ -832,6 +832,25 @@ export const mergeSyncData = (localData, remoteData, retentionDays = 90) => {
|
|
|
832
832
|
const prunedDeletedGoalIds = pruneTombstones(allDeletedGoalIds, tombstoneCutoff);
|
|
833
833
|
const prunedDeletedProjectIds = pruneTombstones(allDeletedProjectIds, tombstoneCutoff);
|
|
834
834
|
|
|
835
|
+
// ── Multi-user roster: last-write-wins per user by updatedAt (keyed by syncId).
|
|
836
|
+
// NOTE: multiUserEnabled is intentionally NOT merged — it's a per-device toggle.
|
|
837
|
+
const localUsers = Array.isArray(localData.users) ? localData.users : [];
|
|
838
|
+
const remoteUsers = Array.isArray(remoteData.users) ? remoteData.users : [];
|
|
839
|
+
const usersById = new Map(localUsers.map(u => [u.syncId ?? u.id, u]));
|
|
840
|
+
for (const u of remoteUsers) {
|
|
841
|
+
const k = u.syncId ?? u.id;
|
|
842
|
+
const prev = usersById.get(k);
|
|
843
|
+
if (!prev || (u.updatedAt || '') > (prev.updatedAt || '')) usersById.set(k, u);
|
|
844
|
+
}
|
|
845
|
+
const mergedUsers = [...usersById.values()];
|
|
846
|
+
|
|
847
|
+
const rosterSig = (arr) => arr
|
|
848
|
+
.map(u => `${u.syncId ?? u.id}:${u.updatedAt || ''}:${u.deleted ? 1 : 0}:${u.name || ''}`)
|
|
849
|
+
.sort().join('|');
|
|
850
|
+
const mergedSig = rosterSig(mergedUsers);
|
|
851
|
+
if (mergedSig !== rosterSig(localUsers)) localChanged = true;
|
|
852
|
+
if (mergedSig !== rosterSig(remoteUsers)) remoteChanged = true;
|
|
853
|
+
|
|
835
854
|
return {
|
|
836
855
|
data: {
|
|
837
856
|
tasks: finalTasks,
|
|
@@ -877,6 +896,7 @@ export const mergeSyncData = (localData, remoteData, retentionDays = 90) => {
|
|
|
877
896
|
minimizedSections: localData.minimizedSections, // UI pref — keep local
|
|
878
897
|
use24HourClock: localData.use24HourClock, // device pref — keep local
|
|
879
898
|
tombstonePrunedBefore: mergedTombstonePrunedBefore,
|
|
899
|
+
users: mergedUsers,
|
|
880
900
|
},
|
|
881
901
|
localChanged,
|
|
882
902
|
remoteChanged
|