diff --git a/src/zen/sync/ZenSpacesSync.sys.mjs b/src/zen/sync/ZenSpacesSync.sys.mjs index 02f510ece..cdabe1a0d 100644 --- a/src/zen/sync/ZenSpacesSync.sys.mjs +++ b/src/zen/sync/ZenSpacesSync.sys.mjs @@ -141,12 +141,21 @@ export class ZenSpacesSyncEngine extends SyncEngine { return ZenSpacesSyncModel.computeChangedIDs(); } - async _reconcile() { + async _reconcile(item) { // Incoming records always apply, and any local divergence re-uploads afterwards // (see ZenSpacesSyncModel.noteApplied). The base reconciliation would // instead drop incoming changes whenever the id is also in the local // changed set, because the snapshot diff stamps its changes with "now" // and the local side would always win the age comparison. + // + // An id we accept must also leave the outgoing set. Projections only + // reflect the applied state after the delayed session collection, so + // uploading the id later in this same sync would overwrite the record + // we just accepted with the stale pre-apply state (and the other + // device would then apply that, endlessly trading states back and + // forth). Divergence that survives the collection re-uploads on a + // later sync through the snapshot diff. + this._modified.delete(item.id); return true; } diff --git a/src/zen/sync/ZenSpacesSyncApplier.sys.mjs b/src/zen/sync/ZenSpacesSyncApplier.sys.mjs index b75e22a95..b2f5dfa9a 100644 --- a/src/zen/sync/ZenSpacesSyncApplier.sys.mjs +++ b/src/zen/sync/ZenSpacesSyncApplier.sys.mjs @@ -33,6 +33,25 @@ class nsZenSpacesSyncApplier { return id ? win.document.getElementById(id) : null; } + /** + * Maps a record's container guid to the local userContextId. A guid with + * no local mapping (its container record hasn't applied here yet) throws, + * failing the record so it redelivers once the container exists . + * + * @param {?string} containerGuid + * @returns {number} + */ + #resolveContainerId(containerGuid) { + if (!containerGuid) { + return 0; + } + const mapped = ZenSpacesSyncModel.contextIdForGuid(containerGuid); + if (mapped === null) { + throw new Error(`unknown container guid ${containerGuid}`); + } + return mapped; + } + /** * @param {Array} records - decrypted incoming records. * @returns {Array} ids of records that failed to apply. @@ -259,8 +278,7 @@ class nsZenSpacesSyncApplier { name: data.name ?? "", icon: data.icon ?? undefined, theme: data.theme ?? null, - containerTabId: - ZenSpacesSyncModel.contextIdForGuid(data.containerGuid) ?? 0, + containerTabId: this.#resolveContainerId(data.containerGuid), }; // A space record re-syncs whenever its child ordering changes, so // only propagate (and especially repaint) when its own fields did. @@ -482,8 +500,7 @@ class nsZenSpacesSyncApplier { } #createTab(win, tabId, data) { - const userContextId = - ZenSpacesSyncModel.contextIdForGuid(data.containerGuid) ?? 0; + const userContextId = this.#resolveContainerId(data.containerGuid); const tab = win.gBrowser.addTrustedTab(data.url, { createLazyBrowser: true, inBackground: true,