@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glance-apps/sync",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
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